天天看點

Java 9 揭秘(20. JDK 9中API層次的改變)

Tips

做一個終身學習的人。

在最後一章内容中,主要介紹以下内容:

  • 下劃線作為新關鍵字
  • 改進使用try-with-resources塊的文法
  • 如何在匿名類中使用

    <>

    操作符
  • 如何在接口中使用私有方法
  • 如何在私有方法上使用

    @SafeVarargs

    注解
  • 如何丢棄子程序的輸出
  • 如何在

    Math

    StrictMath

    類中使用新的方法
  • 如何使用

    Optionals

    流以及

    Optionals

    上的新的操作
  • 如何使用等待提示(spin-wait hints)
  • 對Time API和

    Matcher

    Objects

    類的增強
  • 如何比較數組和數組的一部分
  • Javadoc的增強功能以及如何使用其新的搜尋功能
  • 本地桌面支援JDK 9以及如何使用它們
  • 在對象反序列化過程中如何使用全局和局部過濾器
  • 如何将資料從輸入流傳輸到輸出流以及如何複制和分片緩沖區

Java SE 9有很多小的變化。大的變化包括引入了子產品系統,HTTP/2Client API等。 本章涵蓋了對Java開發人員重要的所有更改。 每個部分涵蓋一個新的主題。 如果興趣了解特定主題,可以直接跳轉到該主題的部分。

示例的源代碼在com.jdojo.misc子產品中,其聲明如下示。

// module-info.java
module com.jdojo.misc {
    requires java.desktop;
    exports com.jdojo.misc;
}
           

該子產品讀取了java.desktop子產品,需要它來實作特定于平台的桌面功能。

一. 下劃線成為關鍵字

在JDK 9中,下劃線(_)是一個關鍵字,不能将其本身用作單個字元辨別符,例如變量名稱,方法名稱,類型名稱等。但是,仍然可以使用下劃線用在多個字元的辨別符名稱中。 考慮下面程式。

// UnderscoreTest.java
package com.jdojo.misc;
public class UnderscoreTest {    
    public static void main(String[] args) {
        // Use an underscore as an identifier. It is a compile-time warning in JDK 8 and a
        // compile-time error in JDK 9.
        int _ = 19;
        System.out.println(_);
        // Use an underscore in multi-character identifiers. They are fine in JDK 8 and JDK 9.
        final int FINGER_COUNT = 20;
        final String _prefix = "Sha";
    }
}
           

在JDK 8中編譯

UnderscoreTest類

會産生兩個警告,用于使用下劃線作為辨別符,一個用于變量聲明,一個用于

System.out.println()

方法調用。 每次使用下劃線時都會産生警告。 JDK 8生成以下兩個警告:

com.jdojo.misc\src\com\jdojo\misc\UnderscoreTest.java:8: warning: '_' used as an identifier
        int _ = 19;
            ^
  (use of '_' as an identifier might not be supported in releases after Java SE 8)
com.jdojo.misc\src\com\jdojo\misc\UnderscoreTest.java:9: warning: '_' used as an identifier
        System.out.println(_);
                           ^
  (use of '_' as an identifier might not be supported in releases after Java SE 8)
2 warnings
Compiling the UnderscoreTest class in JDK 9 generates the following two compile-time errors:
com.jdojo.misc\src\com\jdojo\misc\UnderscoreTest.java:8: error: as of release 9, '_' is a keyword, and may not be used as an identifier
        int _ = 19;
            ^
com.jdojo.misc\src\com\jdojo\misc\UnderscoreTest.java:9: error: as of release 9, '_' is a keyword, and may not be used as an identifier
        System.out.println(_);
                           ^
2 errors
           

JDK 9中的下劃線的特殊含義是什麼,在哪裡使用它? 在JDK 9中,被限制不将其用作辨別符。 JDK設計人員打算在未來的JDK版本中給它一個特殊的含義。 是以,等到JDK 10或11,将它看作具有特殊含義的關鍵字。

二. 改進使用try-with-resources塊的文法

JDK 7向java.lang包添加了一個

AutoCloseable

接口:

public interface AutoCloseable {
    void close() throws Exception;
}
           

JDK 7還添加了一個名為try-with-resources的新塊,可用于使用以下步驟管理

AutoCloseable

對象(或資源):

  • 将該資源的引用配置設定給塊開頭的新聲明的變量。
  • 使用塊中的資源。
  • 當塊的主體被退出時,代表資源的變量的

    close()

    方法将被自動調用。

這避免了在JDK 7之前使用

finally

塊編寫的樣闆代碼。以下代碼片段顯示了開發人員如何管理可關閉的資源,假設存在實作

AutoCloseable

接口的

Resource

類:

/* Prior to JDK 7*/
Resource res = null;
try{
    // Create the resource
    res = new Resource();
    // Work with res here
} finally {
    try {
        if(res != null) {
            res.close();
        }
    } catch(Exception e) {
        e.printStackTrace();
    }
}
           

JDK 7中的try-with-resources塊大大改善了這種情況。 在JDK 7中,可以重寫以前的代碼段,如下所示:

try (Resource res = new Resource()) {
    // Work with res here
}
           

當控制退出try塊時,這段代碼将在

res

上調用

close()

方法。 可以在try塊中指定多個資源,每個資源以分号分隔:

try (Resource res1 = new Resource(); Resource res2 = new Resource()) {
     // Work with res1 and res2 here
}
           

當try塊退出時,兩個資源

res1

res2上

close()

方法将被自動調用。 資源以相反的順序關閉。 在這個例子中,将按順序調用

res2.close()

res1.close()

JDK 7和8要求在try-with-resources塊中聲明引用資源的變量。 如果在方法中收到資源引用作為參數,那麼無法編寫如下所示的邏輯:

void useIt(Resource res) {
    // A compile-time error in JDK 7 and 8
    try(res) {
        // Work with res here
    }
}
           

為了規避此限制,必須聲明另一個新的變量的

Resource

類型,并用參數值初始化它。 以下代碼段顯示了這種方法。 它聲明一個新的參考變量

res1

,當try塊退出時,将調用

close()

方法:

void useIt(Resource res) {        
    try(Resource res1 = res) {
        // Work with res1 here
    }
}
           

JDK 9删除了該限制,必須使用try-with-resource塊為要管理的資源聲明新變量。 現在,可以使用try-with-resources塊來管理

final

或有效的

final

變量來引用資源。 如果使用

final

關鍵字顯式聲明變量,則該變量為

final

// res is explicitly final
final Resource res = new Resource();
           

如果變量在初始化之後從未更改,則該變量實際上是

final

的。 在下面的代碼片段中,盡管

res

變量未被聲明為

final

,但是

res

變量是有效的。 它被初始化,從不再次更改。

void doSomething() {
    // res is effectively final
    Resource res = new Resource();
    res.useMe();
}
           

在JDK 9中,可以這樣寫:

Resource res = new Resource();
try (res) {
    // Work with res here
}
           

如果有多個資源要使用try-with-resources塊來管理,可以這樣做:

Resource res1 = new Resource();
Resource res2 = new Resource();
try (res1; res2) {
    // Use res1 and res2 here
}
           

也可以将JDK 8和JDK 9方法混合在同一個資源塊中。 以下代碼片段在try-with-resources塊中使用兩個預先聲明的有效的

final

變量和一個新聲明的變量:

Resource res1 = new Resource();
Resource res2 = new Resource();
try (res1; res2; Resource res3 = new Resource()) {
    // Use res1, res2, and res3 here
}
           

由于在JDK 7中,在資源塊中聲明的變量是隐含的

final

的。 以下代碼片段明确聲明了這樣一個

final

變量:

Resource res1 = new Resource();
Resource res2 = new Resource();
// Declare res3 explicitly final
try (res1; res2; final Resource res3 = new Resource()) {
    // Use res1, res2, and res3 here            
}
           

我們來看一個完整的例子。 JDK中有幾個類是

AutoCloseable

,例如java.io包中的

InputStream

OutputStream

類。 下面包含實作

AutoCloseable

Resource

類的代碼。

Resource

類的對象可以作為由try-with-resources管理的資源。

id

執行個體變量用于跟蹤資源。 構造方法和其他方法在調用時簡單地列印消息。

// Resource.java
package com.jdojo.misc;
public class Resource implements AutoCloseable {    
    private final long id;
    public Resource(long id) {        
        this.id = id;                
        System.out.printf("Created resource %d.%n", this.id);
    }
    public void useIt() {    
        System.out.printf("Using resource %d.%n", this.id);        
    }
    @Override
    public void close() {
        System.out.printf("Closing resource %d.%n", this.id);
    }
}
           

下面包含了

ResourceTest

類的代碼,它顯示了如何使用JDK 9的新功能,該功能允許使用

final

final

變量來引用這些資源,并使用try-with-resources塊來管理資源。

// ResourceTest.java
package com.jdojo.misc;
public class ResourceTest {
     public static void main(String[] args) {
         Resource r1 = new Resource(1);
         Resource r2 = new Resource(2);
         try(r1; r2) {
             r1.useIt();
             r2.useIt();
             r2.useIt();
         }
         useResource(new Resource(3));
     }
     public static void useResource(Resource res) {
         try(res; Resource res4 = new Resource(4)) {
             res.useIt();
             res4.useIt();
         }
     }
}
           

輸出結果為:

Created resource 1.
Created resource 2.
Using resource 1.
Using resource 2.
Using resource 2.
Closing resource 2.
Closing resource 1.
Created resource 3.
Created resource 4.
Using resource 3.
Using resource 4.
Closing resource 4.
Closing resource 3.
           

三. 如何在匿名類中使用

<>

JDK 7引入了一個鑽石操作符(

<>

),用于調用泛型類的構造方法,隻要編譯器可以推斷通用類型即可。 以下兩個語句是一樣的;第二個使用鑽石操作符:

// Specify the generic type explicitly
List<String> list1 = new ArrayList<String>();
// The compiler infers ArrayList<> as ArrayList<String>
List<String> list2 = new ArrayList<>();
           

建立匿名類時,JDK 7不允許使用鑽石操作符。 以下代碼片段使用帶有鑽石操作符的匿名類來建立

Callable<V>

接口的執行個體:

// A compile-time error in JDK 7 and 8
Callable<Integer> c = new Callable<>() {
    @Override
    public Integer call() {
        return 100;
    }
};
           

上面語句在JDK 7和8中生成以下錯誤:

error: cannot infer type arguments for Callable<V>
        Callable<Integer> c = new Callable<>() {
                                          ^
  reason: cannot use '<>' with anonymous inner classes
  where V is a type-variable:
    V extends Object declared in interface Callable
1 error
           

可以通過指定通用類型代替鑽石運算符來解決此錯誤:

// Works in JDK 7 and 8
Callable<Integer> c = new Callable<Integer>() {
    @Override
    public Integer call() {
        return 100;
    }
};
           

JDK 9就添加了對匿名類中的鑽石操作符的支援,隻要推斷的類型是可表示的。 不能使用具有匿名類的鑽石操作符 —— 即使在JDK 9中,如果推斷的類型是不可表示的。 Java編譯器使用許多不能用Java程式編寫的類型。 可以用Java程式編寫的類型稱為可表示類型。 編譯器知道但不能用Java程式編寫的類型稱為非可表示類型。 例如,String是一個可表示類型,因為可以在程式中使用它來表示類型;然而,

Serializable&CharSequence

不是一個可表示類型的,即使它是編譯器的有效類型。 它是一種交叉類型,表示實作兩個接口

Serializable

CharSequence

的類型。 通用類型定義允許使用交集類型,但不能使用此交集類型聲明變量:

// Not allowed in Java code. Cannot declare a variable of an intersection type.
Serializable & CharSequence var;
// Allowed in Java code
class Magic<T extends Serializable & CharSequence> {        
    // More code goes here
}
           

在JDK 9中,以下是允許使用具有匿名類的鑽石操作符的代碼片段:

// A compile-time error in JDK 7 and 8, but allowed in JDK 9.
Callable<Integer> c = new Callable<>() {
    @Override
    public Integer call() {
        return 100;
    }
};
           

使用

Magic

類的這個定義,JDK 9允許使用像這樣的匿名類:

// Allowed in JDK 9. The <> is inferred as <String>.
Magic<String> m1 = new Magic<>(){
    // More code goes here
};
           

以下使用

Magic

類不會在JDK 9中進行編譯,因為編譯器将通用類型推斷為不可表示類型的交集類型:

// A compile-time error in JDK 9. The <> is inferred as <Serializable & CharSequence>,
// which is non-denotable
Magic<?> m2 = new Magic<>(){
    // More code goes here
};
           

上面的代碼生成以下編譯時錯誤:

error: cannot infer type arguments for Magic<>
        Magic<?> m2 = new Magic<>(){
                               ^
  reason: type argument INT#1 inferred for Magic<> is not allowed in this context
    inferred argument is not expressible in the Signature attribute
  where INT#1 is an intersection type:
    INT#1 extends Object,Serializable,CharSequence
1 error
           

四. 接口中使用私有方法

JDK 8在接口中引入了靜态和預設的方法。 如果必須在這些方法中多次執行相同的邏輯,則隻能重複邏輯或将邏輯移動到另一個類來隐藏實作。 考慮名為

Alphabet

的接口,如下所示。

// Alphabet.java
package com.jdojo.misc;
public interface Alphabet {
    default boolean isAtOddPos(char c) {
        if (!Character.isLetter(c)) {
            throw new RuntimeException("Not a letter: " + c);
        }
        char uc = Character.toUpperCase(c);
        int pos = uc - 64;
        return pos % 2 == 1;
    }
    default boolean isAtEvenPos(char c) {
        if (!Character.isLetter(c)) {
            throw new RuntimeException("Not a letter: " + c);
        }
        char uc = Character.toUpperCase(c);
        int pos = uc - 64;
        return pos % 2 == 0;
    }
}
           

isAtOddpos()

isAtEvenPos()

方法檢查指定的字元是否為奇數或偶數字母順序,假設我們隻處理英文字母。邏輯假定A和a位于位置1,B和b位于位置2等。請注意,兩種方法中的邏輯僅在傳回語句中有所不同。這些方法的整體是相同的,除了最後的語句。你會同意需要重構這個邏輯。将常用邏輯轉移到另一種方法,并從兩種方法調用新方法将是理想的情況。但是,不希望在JDK 8中執行此操作,因為接口僅支援公共方法。這樣做會使第三種方式公開,這将暴露給你不想做的外部世界。

JDK 9允許在接口中聲明私有方法。下顯示了使用包含兩種方法使用的通用邏輯的專用方法的

Alphabet

接口的重構版本。這一次,命名了接口

AlphabetJdk9

,以確定可以在源代碼中包含這兩個版本。現有的兩種方法成為一行代碼。

// AlphabetJdk9.java
package com.jdojo.misc;
public interface AlphabetJdk9 {
    default boolean isAtOddPos(char c) {
        return getPos(c) % 2 == 1;
    }
    default boolean isAtEvenPos(char c) {
        return getPos(c) % 2 == 0;
    }
    private int getPos(char c) {
        if (!Character.isLetter(c)) {
            throw new RuntimeException("Not a letter: " + c);
        }
        char uc = Character.toUpperCase(c);
        int pos = uc - 64;
        return pos;
    }
}
           

在JDK 9之前,接口中的所有方法都被隐式公開。 記住這些适用于Java中所有程式的簡單規則:

  • private

    方法不能被繼承,是以不能被重寫。
  • final

    方法不能被重寫。
  • abstract

    方法是可以繼承的,意圖是被重寫。
  • default

    方法是一個執行個體方法,并提供預設實作。 這意味着可以被重寫。

通過在JDK 9中引入私有方法,需要在接口聲明方法時遵循一些規則。 修飾符的所有組合——

abstract

public

private

static

。 下表列出了在JDK 9中的接口的方法聲明中支援和不支援的修飾符的組合。請注意,接口的方法聲明中不允許使用

fjinal

修飾符。 根據這個清單,可以在一個非抽象,非預設的執行個體方法或一個靜态方法的接口中有一個私有方法。

Modifiers Supported? Description
public static Yes 從JDK 8開始支援
public abstract 從JDK 1開始支援
public default
private static 從JDK 9開始支援
private 從JDK 9開始支援,這是一個非抽象的執行個體方法
private abstract No 這種組合沒有意義
private default 這種組合沒有意義,私有方法不被繼承,是以不能被重寫,而如果需要,預設方法的本意是需要重寫的。

五. 私有方法上的

@SafeVarargs

具體化類型表示其資訊在運作時完全可用,例如

String

Integer

List

等。非具體化類型表示其資訊已由編譯器使用類型擦除(例如

List<String>

)删除, 編譯後成為

List

當使用非具體化類型的可變(var-args)參數時,該參數的類型僅供編譯器使用。 編譯器将擦除參數化類型,并将其替換為無界類型的實際類型為

Object []

的數組,其類型為有界類型的上限的特定數組。 編譯器不能保證對方法體内的這種非具體化可變參數執行的操作是安全的。 考慮以下方法的定義:

<T> void print(T... args) {
    for(T element : args) {
        System.out.println(element);
    }
}
           

編譯器将用

print(Object[] args)

替換

print(T… args)

。 該方法的主體對

args

參數不執行任何不安全的操作。考慮執行以下不安全操作的方法聲明:

public static void unsafe(List<Long>... rolls) {
    Object[] list = rolls;        
    list[0] = List.of("One", "Two");
    // Unsafe!!! Will throw a ClassCastException at runtime
    Long roll = rolls[0].get(0);
}
           

unsafe()

方法将

rolls

(它是

List<String>

的數組)配置設定給一個

Object []

數組。 它将

List<String>

存儲到

Object []

的第一個元素中,這也是允許的。

rolls [0]

的類型被推斷為

List <Long>

get(0)

方法應該傳回一個

Long

。 但是,運作時會抛出一個

ClassCastException

,因為

rolls[0].get(0)

傳回的實際類型是

String

,而不是

Long

當聲明使用非具體化的可變參數類型的

print()

unsafe()

方法時,Java編譯器會發出如下所示的未經檢查的警告:

warning: [unchecked] Possible heap pollution from parameterized vararg type List<Long>
    public static void unsafe(List<Long>... rolls) {
                                            ^
           

編譯器會為此類方法聲明生成警告,并為每次調用該方法發出警告。 如果

unsafe()

方法被調用五次,将收到六個警告(一個用于聲明,五個調用)。 可以在方法聲明和調用站點上使用

@SafeVarargs

注解來抑制這些警告。 通過将此注解添加到方法聲明中,確定方法的使用者和編譯器在方法的主體中,不對非具體化的可變參數類型執行任何不安全的操作。 你的保證是足夠好的,編譯器不發出警告。 但是,如果你的保證在運作時證明是不真實的,則運作時将抛出适當類型的異常。

在JDK 9之前,可以在以下可執行的(構造函數和方法)上使用

@SafeVarargs

注解:

  • 構造方法
  • static

    方法
  • final

構造方法,

static

方法和

final

方法是不可重寫的。 允許

@SafeVarargs

注解僅适用于不可重寫的可執行的代碼的想法,是為了保護開發人員在重寫可執行代碼上違反注解限制的重寫可執行檔案上使用此注解。 假設有一個類

X

,它包含一個方法

m1()

,它包含一個

@SafeVarargs

。 進一步假設有一個從類

X

繼承的類

Y

。類

Y

可以重寫繼承的方法

m1()

,并可能有不安全的操作。 這将産生運作時驚喜,因為開發人員可以根據父類

X

編寫代碼,并且可能不會期望任何不安全的操作,如其方法

m1()

所承諾的。

私有方法也是不可重寫的,是以JDK 9決定在私有方法上允許

@SafeVarargs

注解。 下面顯示了一個使用

@SafeVarargs

注解的私有方法的類。 在JDK 9中可以具有

@SafeVarargs

注釋的可執行清單如下所示:

  • static

  • final

  • 私有方法
// SafeVarargsTest.java
package com.jdojo.misc;
public class SafeVarargsTest {
    // Allowed in JDK 9
    @SafeVarargs
    private <T> void print(T... args) {
        for(T element : args) {
            System.out.println(element);
        }
    }
    // More code goes here
}
           

在JDK 8中編譯此類會生成以下錯誤,它指出

@SafeVarargs

不能在非

final

方法中使用,這是一種私有方法。 需要使用

-Xlint:unchecked

選項編譯源代碼以檢視錯誤。

com\jdojo\misc\SafeVarargsTest.java:6: error: Invalid SafeVarargs annotation. Instance method <T> print(T...) is not final.
    private <T> void print(T... args) {
                     ^
  where T is a type-variable:
    T extends Object declared in method <T>print(T...)
           

六. 丢棄子程序的輸出

JDK 9向

ProcessBuilder.Redirect

嵌套類添加了一個

DISCARD

新常量。 它的類型是

ProcessBuilder.Redirect

。 當要丢棄輸出時,可以将其用作子程序的輸出和錯誤流的目标。 實作通過寫入作業系統特定的“空檔案(null file)”來丢棄輸出。下面包含一個完整的程式,顯示如何丢棄子程序的輸出。

// DiscardProcessOutput.java
package com.jdojo.misc;
import java.io.IOException;
public class DiscardProcessOutput {
    public static void main(String[] args) {
        System.out.println("Using Redirect.INHERIT:");
        startProcess(ProcessBuilder.Redirect.INHERIT);
        System.out.println("\nUsing Redirect.DISCARD:");
        startProcess(ProcessBuilder.Redirect.DISCARD);
    }
    public static void startProcess(ProcessBuilder.Redirect outputDest) {        
        try {
            ProcessBuilder pb = new ProcessBuilder()
                    .command("java", "-version")                    
                    .redirectOutput(outputDest)
                    .redirectError(outputDest);
            Process process = pb.start();
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
           
Using Redirect.INHERIT:
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+157)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+157, mixed mode)
Using Redirect.DISCARD:
Listing 20-8.
Discarding a Process’ Outputs
           

startProcess()

方法通過使用

-version

參數啟動java程式來開始一個程序。 該方法通過輸出目的地參數。 第一次,

Redirect.INHERIT

作為輸出目的地傳遞,這允許子程序使用标準輸出和标準錯誤來列印消息。 第二次,

Redirect.DISCARD

作為輸出目标傳遞,沒有子程序的輸出。

七.

StrictMath

類中的新方法

JDK在java.lang包中包含兩個類

Math

StrictMath

。 這兩個類隻包含靜态成員,它們包含提供基本數字操作(如平方根,絕對值,符号,三角函數和雙曲線函數)的方法。 為什麼有兩個類來提供類似的操作?

Math

類不需要在所有實作中傳回相同的結果。 這允許它使用庫的本地實作來進行操作,這可能會在不同的平台上傳回稍微不同的結果。

StrictMath

類必須在所有實作中傳回相同的結果。

Math

類中的許多方法都調用

StrictMath

類的方法。 JDK 9将以下靜态方法添加到

Math

StrictMath

類中:

long floorDiv(long x, int y)
int floorMod(long x, int y)
double fma(double x, double y, double z)
float fma(float x, float y, float z)
long multiplyExact(long x, int y)
long multiplyFull(int x, int y)
long multiplyHigh(long x, long y)
           

floorDiv()

方法傳回小于或等于将

x

除以

y

的代數商的最大長度值。 當兩個參數具有相同的符号時,除法結果将向零舍入(截斷模式)。 當它們具有不同的符号時,除法結果将朝向負無窮大。 當被除數為

Long.MIN_VALUE

而除數為-1時,該方法傳回

Long.MIN_VALUE

。 當除數為零時抛出

ArithmeticException

floorMod()

方法傳回最小的模數,等于

x - (floorDiv(x, y) * y)
           

最小模數的符号與除數y相同,在

-abs(y) < r < +abs(y)

範圍内。

fma()

方法對應于IEEE 754-2008中定義的fusedMultiplyAdd操作。 它傳回

(a * b + c)

的結果,如同無限範圍和精度一樣,并舍入一次到最接近的

double

float

值。 舍入是使用到最近的偶數舍入模式完成的。 請注意,

fma()

方法傳回比表達式

(a * b + c)

更準确的結果,因為後者涉及兩個舍入誤差——一個用于乘法,另一個用于加法,而前者僅涉及一個舍入誤差。

multiplyExact()

方法傳回兩個參數的乘積,如果結果超過

long

類型最大能表示的數字,則抛出

ArithmeticException

異常。

multiplyFull()

方法傳回兩個參數的确切乘積。

multiplyHigh()

方法傳回長度是兩個64位參數的128位乘積的最高有效64位。 當乘以兩個64位長的值時,結果可能是128位值。 是以,該方法傳回

significant (high)

64位。 下面包含一個完整的程式,用于說明在

StrictMath

類中使用這些新方法。

// StrictMathTest.java
package com.jdojo.misc;
import static java.lang.StrictMath.*;
public class StrictMathTest {
    public static void main(String[] args) {
        System.out.println("Using StrictMath.floorDiv(long, int):");
        System.out.printf("floorDiv(20L, 3) = %d%n", floorDiv(20L, 3));
        System.out.printf("floorDiv(-20L, -3) = %d%n", floorDiv(-20L, -3));
        System.out.printf("floorDiv(-20L, 3) = %d%n", floorDiv(-20L, 3));
        System.out.printf("floorDiv(Long.Min_VALUE, -1) = %d%n", floorDiv(Long.MIN_VALUE, -1));
        System.out.println("\nUsing StrictMath.floorMod(long, int):");
        System.out.printf("floorMod(20L, 3) = %d%n", floorMod(20L, 3));
        System.out.printf("floorMod(-20L, -3) = %d%n", floorMod(-20L, -3));
        System.out.printf("floorMod(-20L, 3) = %d%n", floorMod(-20L, 3));
        System.out.println("\nUsing StrictMath.fma(double, double, double):");
        System.out.printf("fma(3.337, 6.397, 2.789) = %f%n", fma(3.337, 6.397, 2.789));
        System.out.println("\nUsing StrictMath.multiplyExact(long, int):");
        System.out.printf("multiplyExact(29087L, 7897979) = %d%n",
                multiplyExact(29087L, 7897979));
        try {
            System.out.printf("multiplyExact(Long.MAX_VALUE, 5) = %d%n",
                    multiplyExact(Long.MAX_VALUE, 5));
        } catch (ArithmeticException e) {
            System.out.println("multiplyExact(Long.MAX_VALUE, 5) = " + e.getMessage());
        }
        System.out.println("\nUsing StrictMath.multiplyFull(int, int):");
        System.out.printf("multiplyFull(29087, 7897979) = %d%n", multiplyFull(29087, 7897979));
        System.out.println("\nUsing StrictMath.multiplyHigh(long, long):");
        System.out.printf("multiplyHigh(29087L, 7897979L) = %d%n",
                multiplyHigh(29087L, 7897979L));
        System.out.printf("multiplyHigh(Long.MAX_VALUE, 8) = %d%n",
                multiplyHigh(Long.MAX_VALUE, 8));
    }
}
           
Using StrictMath.floorDiv(long, int):
floorDiv(20L, 3) = 6
floorDiv(-20L, -3) = 6
floorDiv(-20L, 3) = -7
floorDiv(Long.Min_VALUE, -1) = -9223372036854775808
Using StrictMath.floorMod(long, int):
floorMod(20L, 3) = 2
floorMod(-20L, -3) = -2
floorMod(-20L, 3) = 1
Using StrictMath.fma(double, double, double):
fma(3.337, 6.397, 2.789) = 24.135789
Using StrictMath.multiplyExact(long, int):
multiplyExact(29087L, 7897979) = 229728515173
multiplyExact(Long.MAX_VALUE, 5) = long overflow
Using StrictMath.multiplyFull(int, int):
multiplyFull(29087, 7897979) = 229728515173
Using StrictMath.multiplyHigh(long, long):
multiplyHigh(29087L, 7897979L) = 0
multiplyHigh(Long.MAX_VALUE, 8) = 3
           

八. 對

ClassLoader

類的更改

JDK 9将以下構造方法和方法添加到

java.lang.ClassLoader

protected ClassLoader(String name, ClassLoader parent)
public String getName()
protected Class<?> findClass(String moduleName, String name)
protected URL findResource(String moduleName, String name) throws IOException
public Stream<URL> resources(String name)
public final boolean isRegisteredAsParallelCapable()
public final Module getUnnamedModule()
public static ClassLoader getPlatformClassLoader()
public final Package getDefinedPackage(String name)
public final Package[] getDefinedPackages()
           

這些方法具有直覺的名稱。受保護的構造方法和方法适用于開發人員建立新的類加載器。

一個類加載器可以有一個可選的名稱,可以使用

getName()

方法。 當類加載器沒有名稱時,該方法傳回null。 Java運作時将包括堆棧跟蹤和異常消息中的類加載程式名稱(如果存在)。 這将有助于調試。

resources()

方法傳回使用特定資源名稱找到的所有資源的URL流。

每個類加載器都包含一個未命名的子產品,該子產品包含該類加載器從類路徑加載的所有類型。

getUnnamedModule()

方法傳回類加載器的未命名子產品的引用。

靜态

getPlatformClassLoader()

方法傳回平台類加載器的引用。

九.

Optional<T>

JDK 9中的

java.util.Optional<T>

類已經添加了三種新方法:

void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
Stream<T> stream()
           

在描述這些方法并提供一個顯示其使用的完整程式之前,請考慮以下

Optional<Integer>

清單:

List<Optional<Integer>> optionalList = List.of(Optional.of(1),
                                               Optional.empty(),
                                               Optional.of(2),
                                               Optional.empty(),
                                               Optional.of(3));
           

該清單包含五個元素,其中兩個為空的

Optional

,三個包含值為1,2和3。

ifPresentOrElse()

方法可以提供兩個備選的操作。 如果存在值,則使用該值執行指定的操作。 否則,它執行指定的可選值。 以下代碼片段使用流列印清單中的所有元素,如果

Optional

不為空,則列印其具體的值,為空的話,替換為“Empty”字元串。

optionalList.stream()
            .forEach(p -> p.ifPresentOrElse(System.out::println,
                                            () -> System.out.println("Empty")));
           

列印結果為:

1
Empty
2
Empty
3
           

of

方法如果

Optional

有值則傳回

Optional

本身。否則,傳回指定

supplier

Optional

。以下代碼從

Optional

清單中傳回一個流,并使用

of()

方法映射空的

Optionals

為帶有預設值0的

Optionals

.

optionalList.stream()
            .map(p -> p.or(() -> Optional.of(0)))
            .forEach(System.out::println);
Optional[1]
Optional[0]
Optional[2]
Optional[0]
Optional[3]
           

stream()

方法傳回包含

Optional

中存在的值的元素的順序流。 如果

Optional

為空,則傳回一個空的流。 假設有一個

Optional

的清單,并且想收集另一個清單中的所有存在的值。 可以在Java 8中如下實作:

// list8 will contain 1, 2, and 3
List<Integer> list8 = optionalList.stream()
                                  .filter(Optional::isPresent)
                                  .map(Optional::get)
                                  .collect(toList());
           

必須使用過濾器過濾掉所有空的

Optionals

,并将剩餘的可選項映射到其值。 使用JDK 9中的新的

stream()

方法,可以将

filter()

map()

操作組合成一個

flatMap()

操作,如下所示:

// list9 contain 1, 2, and 3
List<Integer> list9 = optionalList.stream()
                                  .flatMap(Optional::stream)
                                  .collect(toList());
           

下面包含一個完整的程式來示範使用這些方法。

// OptionalTest.java
package com.jdojo.misc;
import java.util.List;
import java.util.Optional;
import static java.util.stream.Collectors.toList;
public class OptionalTest {
    public static void main(String[] args) {
        // Create a list of Optional<Integer>
        List<Optional<Integer>> optionalList = List.of(
                Optional.of(1),
                Optional.empty(),
                Optional.of(2),
                Optional.empty(),
                Optional.of(3));
        // Print the original list
        System.out.println("Original List: " + optionalList);
        // Using the ifPresentOrElse() method
        optionalList.stream()
                    .forEach(p -> p.ifPresentOrElse(System.out::println,
                                                    () -> System.out.println("Empty")));
        // Using the or() method
        optionalList.stream()
                    .map(p -> p.or(() -> Optional.of(0)))
                    .forEach(System.out::println);
        // In Java 8
        List<Integer> list8 = optionalList.stream()
                                          .filter(Optional::isPresent)
                                          .map(Optional::get)
                                          .collect(toList());
        System.out.println("List in Java 8: " + list8);
        // In Java 9
        List<Integer> list9 = optionalList.stream()
                                          .flatMap(Optional::stream)
                                          .collect(toList());
        System.out.println("List in Java 9: " + list9);
    }
}
           
Original List: [Optional[1], Optional.empty, Optional[2], Optional.empty, Optional[3]]
1
Empty
2
Empty
3
Optional[1]
Optional[0]
Optional[2]
Optional[0]
Optional[3]
List in Java 8: [1, 2, 3]
List in Java 9: [1, 2, 3]
           

十.

CompletableFuture<T>

中的新方法

在JDK 9 中,java.util.concurrent包中的

CompletableFuture<T>

類添加了以下新方法:

<U> CompletableFuture<U> newIncompleteFuture()
Executor defaultExecutor()
CompletableFuture<T> copy()
CompletionStage<T> minimalCompletionStage()
CompletableFuture<T> completeAsync(Supplier<? extends T> supplier, Executor executor)
CompletableFuture<T> completeAsync(Supplier<? extends T> supplier)
CompletableFuture<T> orTimeout(long timeout, TimeUnit unit)
CompletableFuture<T> completeOnTimeout(T value, long timeout, TimeUnit unit)
static Executor delayedExecutor(long delay, TimeUnit unit, Executor executor)
static Executor delayedExecutor(long delay, TimeUnit unit)
static <U> CompletionStage<U> completedStage(U value)
static <U> CompletableFuture<U> failedFuture(Throwable ex)
static <U> CompletionStage<U> failedStage(Throwable ex)
           

有關這些方法的更多資訊,請查閱類的Javadoc。

十一. 旋轉等待提示(Spin-Wait Hints)

在多線程程式中,線程通常需要協調。一個線程可能必須等待另一個線程來更新

volatile

變量。 當

volatile

變量以某個值更新時,第一個線程可以繼續。 如果等待可能更長,建議第一個線程通過睡眠或等待來放棄CPU,并且可以在恢複工作時通知它。 然而,使線程睡眠或等待具有延遲。 為了短時間等待并減少延遲,線程通常通過檢查某個條件為真來循環等待。 考慮使用循環等待為

dataReady

volatile

變量等于true的類中代碼:

volatile boolean dataReady;
...
@Override
public void run() {
    // Wait until data is ready
    while (!dataReady) {
        // No code
    }
    processData();
}
private void processData() {
    // Data processing logic goes here
}
           

該代碼中的

while

循環稱為spin-loop,busy-spin,busy-wait或spin-wait。

while

保持循環,直到

dataReady

變量為true。

由于不必要的資源使用而不耐心等待,是以通常是需要的。 在這個例子中,優點是一旦

dataReady

變量變為true,線程就會開始處理資料。 然而,犧牲性能和功耗,因為線程正在活躍地循環。

某些處理器可以暗示線程處于旋轉等待狀态,如果可能,可以優化資源使用。 例如,x86處理器支援一個PAUSE指令來訓示一個旋轉等待。 該指令延遲下一條指令對線程的執行有限的少量時間,進而提高了資源的使用。

Thread

類添加了一個新的靜态

onSpinWait()

方法。 對處理器來說,這是一個純粹的提示,即調用者線程暫時無法繼續,是以可以優化資源使用。 當底層平台不支援這種提示時,此方法的可能實作可能是無效的。

下面包含示例代碼。 請注意,程式的語義不會通過使用旋轉等待提示來更改。 如果底層硬體支援提示,它可能會更好。

// SpinWaitTest.java
package com.jdojo.misc;
public class SpinWaitTest implements Runnable {
    private volatile boolean dataReady = false;
    @Override
    public void run() {
        // Wait while data is ready
        while (!dataReady) {
            // Hint a spin-wait
            Thread.onSpinWait();
        }
        processData();
    }
    private void processData() {
        // Data processing logic goes here
    }
    public void setDataReady(boolean dataReady) {
        this.dataReady = dataReady;
    }
}
           

十二. Time API 增強

Time API已在JDK 9中得到增強,并在多個接口和類中使用了大量新方法。 Time API由

java.time.*

包組成,它們位于java.base子產品中。

1.

Clock

Clock

類中已經添加了以下方法:

static Clock tickMillis(ZoneId zone)
           

tickMillis()

方法傳回一個時鐘,提供了整個毫秒的目前瞬間記錄。 時鐘使用最好的系統時鐘。時鐘以高于毫秒的精度截斷時間值。 調用此方法等同于以下内容:

Clock.tick(Clock.system(zone), Duration.ofMillis(1))
           

2.

Duration

可以根據用途将

Duration

類中的新方法分為三類:

  • 将持續時間劃分另一個持續時間的方法
  • 根據特定時間機關擷取持續時間的方法和擷取特定部分持續時間(如天,小時,秒等)的方法。
  • 将持續時間縮短到特定時間機關的方法

    在這裡使用持續時間為23天,3小時45分30秒。 以下代碼片段将其建立

    Duration

    對象,并将其引用儲存在

    compTime

    的變量中:
// Create a duration of 23 days, 3 hours, 45 minutes, and 30 seconds
Duration compTime = Duration.ofDays(23)
                            .plusHours(3)
                            .plusMinutes(45)
                            .plusSeconds(30);
System.out.println("Duration: " + compTime);
           
Duration: PT555H45M30S
           

通過将這些日期乘以24小時後,輸出顯示,此持續時間代表555小時,45分鐘和30秒。

(1). 将持續時間劃分另一個持續時間

此類别中隻有一種方法:

long dividedBy(Duration divisor)
           

divideBy()

方法可以将持續時間劃分另一個持續時間。 它傳回特定除數在調用該方法的持續時間内發生的次數。 要知道在這段時間内有多少整周,可以使用七天作為持續時間來調用

divideBy()

方法。 以下代碼片段顯示了如何計算持續時間内的整天,周和小時數:

long wholeDays = compTime.dividedBy(Duration.ofDays(1));
long wholeWeeks = compTime.dividedBy(Duration.ofDays(7));
long wholeHours = compTime.dividedBy(Duration.ofHours(7));
System.out.println("Number of whole days: " + wholeDays);
System.out.println("Number of whole weeks: " + wholeWeeks);
System.out.println("Number of whole hours: " + wholeHours);
           
Number of whole days: 23
Number of whole weeks: 3
Number of whole hours: 79
           
(2). 轉換和檢索部分持續時間

此類别中的

Duration

類添加了幾種方法:

long toDaysPart()
int toHoursPart()
int toMillisPart()
int toMinutesPart()
int toNanosPart()
long toSeconds()
int toSecondsPart()
           

Duration

類包含兩組方法。它們被命名為

toXxx()

toXxxPart()

,其中

Xxx

可以是Days,Hours,Minutes,Seconds,Millis和Nanos。在此清單中,可能會注意到包含

toDaysPart()

,但是丢失了

toDays()

。如果看到某些

Xxx

中缺少一個方法,則表示這些方法已經存在于JDK 8中。例如,從JDK 8開始,

toDays()

方法已經在

Duration

類中。

名為

toXxx()

的方法将持續時間轉換為

Xxx

時間機關并傳回整個部分。名為

toXxxPart()

的方法會以幾天為機關,以時間為機關分解持續時間:小時:分鐘:秒:毫秒:納秒,并從中傳回

Xxx

部分。在這個例子中,

toDays()

将會将持續時間轉換為天數并傳回整個部分,這是23。

toDaysPart()

會将持續時間分解為23天:3Hours:45Minutes:30Seconds:0Millis:0Nanos,并傳回第一部分,這是23。我們将相同的規則應用于

toHours()

toHoursPart()

方法。

toHours()

方法會将持續時間轉換為小時,并傳回整個小時數,這是555。

toHoursPart()

方法會将持續時間與

toDaysPart()

方法一樣分分解為一部分,并傳回小時部分,這是。以下代碼片段顯示了幾個例子:

System.out.println("toDays(): " + compTime.toDays());
System.out.println("toDaysPart(): " + compTime.toDaysPart());
System.out.println("toHours(): " + compTime.toHours());
System.out.println("toHoursPart(): " + compTime.toHoursPart());
System.out.println("toMinutes(): " + compTime.toMinutes());
System.out.println("toMinutesPart(): " + compTime.toMinutesPart());
           
toDays(): 23
toDaysPart(): 23
toHours(): 555
toHoursPart(): 3
toMinutes(): 33345
toMinutesPart(): 45
           
(3). 截取持續時間

Duration

類隻添加了一種方法:

Duration truncatedTo(TemporalUnit unit)
           

truncatedTo()

方法傳回一個持續時間的副本,其概念時間機關小于被截斷的指定機關。 指定的時間機關必須為DAYS或更小。 指定大于DAYS(如WEEKS和YEARS)的時間機關會引發運作時異常。

JDK 8中的

LocalTime

Instant

類中已經存在

truncatedTo(TemporalUnit unit)

以下代碼片段顯示了如何使用此方法:

System.out.println("Truncated to DAYS: " + compTime.truncatedTo(ChronoUnit.DAYS));
System.out.println("Truncated to HOURS: " + compTime.truncatedTo(ChronoUnit.HOURS));
System.out.println("Truncated to MINUTES: " + compTime.truncatedTo(ChronoUnit.MINUTES));
           
Truncated to DAYS: PT552H
Truncated to HOURS: PT555H
Truncated to MINUTES: PT555H45M
           

持續時間為23Days:3Hours:45Minutes:30Seconds:0Millis:0Nanos。 當将其截斷為DAYS時,小于天數的所有部分将被删除,并傳回23天,這與輸出中顯示的552小時相同。 當截斷到HOURS時,它會将所有小于小時的部分删除掉,并傳回555小時。 将其截斷到MINUTES可保留分鐘的部分,删除小于分鐘的部分。

3.

ofInstant()

工廠方法

Time API旨在提高開發人員的便利和效率。 有一些經常使用的用例,日期和時間之間的轉換強制開發人員使用更多的方法調用而不是必需的。 兩個這樣的用例是:

  • java.util.Date

    轉換為

    LocalDate

  • Instant

    LocalDate

    LocalTime

JDK 9在

LocalDate

LocalTime

中添加了一個靜态工廠方法,ofInstant(Instant instant, ZoneId zone)

,以簡化這兩種類型的轉換。 在

ZonedDateTime

OffsetDateTime

LocalDateTime

OffsetTime

類中,JDK 8已經有了這種工廠方法。 以下代碼片段顯示了JDK 8和JDK 9的兩種方法——将

java.util.Date

LocalDate

// In JDK 8
Date dt = new Date();
LocalDate ld= dt.toInstant()
                 .atZone(ZoneId.systemDefault())
                 .toLocalDate();
System.out.println("Current Local Date: " + ld);
        
// In JDK 9
LocalDate ld2 = LocalDate.ofInstant(dt.toInstant(), ZoneId.systemDefault());
System.out.println("Current Local Date: " + ld2);
           
Current Local Date: 2017-02-11
Current Local Date: 2017-02-11
           

以下代碼片段顯示了兩種方式,在DK 8和JDK 9,将

Instant

LocalDate

LocalTime

// In JDK 8
Instant now = Instant.now();
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime zdt = now.atZone(zone);
LocalDate ld3 = zdt.toLocalDate();
LocalTime lt3 = zdt.toLocalTime();
System.out.println("Local Date: " + ld3 + ", Local Time:" + lt3);
// In JDK 9        
LocalDate ld4 = LocalDate.ofInstant(now, zone);
LocalTime lt4 = LocalTime.ofInstant(now, zone);
System.out.println("Local Date: " + ld4 + ", Local Time:" + lt4);
Local Date: 2017-02-11, Local Time:22:13:31.919339400
Local Date: 2017-02-11, Local Time:22:13:31.919339400
           
Local Date: 2017-02-11, Local Time:22:13:31.919339400
Local Date: 2017-02-11, Local Time:22:13:31.919339400
           

4. 擷取紀元秒

有時想從

LocalDat

e,

LocalTime

OffsetTime

擷取自1970-01-01T00:00:00Z的時代以來的秒數。 在JDK 8中,

OffsetDateTime

類包含一個

toEpochSecond()

方法。 如果要從

ZonedDateTime

擷取時代以來的秒數,則必須使用它的

toOffsetDateTime()

方法将其轉換為

OffsetDateTime

,并使用

OffsetDateTime

類的t

oEpochSecond()

方法。 JDK 8沒有包含用于

LocalDate

LocalTime

OffsetTime

類的

toEpochSecond()

方法。 JDK 9添加了這些方法:

LocalDate.toEpochSecond(LocalTime time, ZoneOffset offset)
LocalTime.toEpochSecond(LocalDate date, ZoneOffset offset)
OffsetTime.toEpochSecond(LocalDate date)
           

為什麼這些類的

toEpochSecond()

方法的簽名不同? 要從時代1970-01-01T00:00:00Z獲得秒數,需要定義另一個

Instant

。 一個

Instant

可以用三個部分定義:日期,時間,區域偏移。

LocalDate

LocalTime

類隻包含一個

Instant

的三個部分之一。

OffsetTime

類包含兩個部分,一個時間和一個偏移量。 缺少的部分需要被這些類指定為參數。 是以,這些類包含

toEpochSecond()

方法,該方法的參數指定了用于定義

Instant

的缺失部分。 以下代碼片段使用相同的

Instant

從三個類中擷取時代的秒數:

LocalDate ld = LocalDate.of(2017, 2, 12);
LocalTime lt = LocalTime.of(9, 15, 45);
ZoneOffset offset = ZoneOffset.ofHours(6);
OffsetTime ot = OffsetTime.of(lt, offset);
long s1 = ld.toEpochSecond(lt, offset);
long s2 = lt.toEpochSecond(ld, offset);
long s3 = ot.toEpochSecond(ld);
System.out.println("LocalDate.toEpochSecond(): " + s1);
System.out.println("LocalTime.toEpochSecond(): " + s2);
System.out.println("OffsetTime.toEpochSecond(): " + s3);
LocalDate.toEpochSecond(): 1486869345
LocalTime.toEpochSecond(): 1486869345
OffsetTime.toEpochSecond(): 1486869345
           

5. LocalDate流

JDK 9可以輕松地跨越兩個給定日期之間的所有日期,可以是某時的一天或給定一個區間時段。 以下兩種方法已添加到

LocalDate

Stream<LocalDate> datesUntil(LocalDate endExclusive)
Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step)
           

這些方法産生

LocalDates

的順序排序流。 流中的第一個元素是調用該方法的

LocalDate

datesUntil(LocalDate endExclusive)

方法一次一天地增加流中的元素,而

datesUntil(LocalDate endExclusive, Period step)

方法會按照指定的步驟增加它們。 指定的結束日期是排他的。 可以在傳回的流上執行幾個有用的計算。 以下代碼片段計算了2017年的星期數。請注意,代碼使用2018年1月1日作為最後一個日期,它是排他的,這将使流傳回2017年的所有日期。

long sundaysIn2017 = LocalDate.of(2017, 1, 1)
                              .datesUntil(LocalDate.of(2018, 1, 1))
                              .filter(ld -> ld.getDayOfWeek() == DayOfWeek.SUNDAY)
                              .count();        
System.out.println("Number of Sundays in 2017: " + sundaysIn2017);
           

列印的結果為:

Number of Sundays in 2017: 53
           

以下代碼片段将于2017年1月1日(含)之間列印至2022年1月1日(不包含),即星期五落在本月十三日的日期:

LocalDate.of(2017, 1, 1)
         .datesUntil(LocalDate.of(2022, 1, 1))
         .filter(ld -> ld.getDayOfMonth() == 13 && ld.getDayOfWeek() == DayOfWeek.FRIDAY)
         .forEach(System.out::println);
           
Fridays that fall on 13th of the month between 2017 - 2021 (inclusive):
2017-01-13
2017-10-13
2018-04-13
2018-07-13
2019-09-13
2019-12-13
2020-03-13
2020-11-13
2021-08-13
           

以下代碼片段列印2017年每月的最後一天:

System.out.println("Last Day of months in 2017:");
LocalDate.of(2017, 1, 31)                
         .datesUntil(LocalDate.of(2018, 1, 1), Period.ofMonths(1))
         .map(ld -> ld.format(DateTimeFormatter.ofPattern("EEE MMM dd, yyyy")))
         .forEach(System.out::println);
           
Last Day of months in 2017:
Tue Jan 31, 2017
Tue Feb 28, 2017
Fri Mar 31, 2017
Sun Apr 30, 2017
Wed May 31, 2017
Fri Jun 30, 2017
Mon Jul 31, 2017
Thu Aug 31, 2017
Sat Sep 30, 2017
Tue Oct 31, 2017
Thu Nov 30, 2017
Sun Dec 31, 2017
           

6. 新的格式化選項

JDK 9向Time API添加了一些格式化選項。 以下部分将詳細介紹這些改動。

1. 修正儒略日格式

可以在日期時間格式化程式模式中使用小寫字母g,它将日期部分格式化為修正儒略日作為整數。 可以多次重複多次使用g,例如ggg,如果結果中的位數小于g指定的數目,則會對結果進行零填充。 http://www.unicode.org/reports/tr35/tr35-41/tr35-dates.html#Date_Format_Patterns上的定義了格式化程式中字母g的含義如下:

修正儒略日。 這與以往的修正儒略日不同。 首先,它在當地時區午夜,而不是格林尼治标準時間中午劃定天數。 二是本地數字; 也就是說,這取決于當地的時區。 它可以被認為是包含所有日期相關字段的單個數字。

大寫字母G被定義為JDK 8中的日期和時間格式化器符号。

以下代碼片段顯示了如何使用修正儒略日字元g格式化

ZonedDateTime

ZonedDateTime zdt = ZonedDateTime.now();
System.out.println("Current ZonedDateTime: " + zdt);               
System.out.println("Modified Julian Day (g): " +
                zdt.format(DateTimeFormatter.ofPattern("g")));
System.out.println("Modified Julian Day (ggg): " +
                zdt.format(DateTimeFormatter.ofPattern("ggg")));
System.out.println("Modified Julian Day (gggggg): " +
                zdt.format(DateTimeFormatter.ofPattern("gggggg")));
Current ZonedDateTime: 2017-02-12T11:49:03.364431100-06:00[America/Chicago]
           
Modified Julian Day (g): 57796
Modified Julian Day (ggg): 57796
Modified Julian Day (gggggg): 057796
           

2. 通用時區名稱

JDK 8有兩個字母V和z來格式化日期和時間的時區。 字母V産生區域ID,例如“America / Los_Angeles; Z; -08:30”,字母z産生區域名稱,如中央标準時間和CST。

JDK 9将小寫字母v添加為格式化符号,生成通用的非定位區域名稱,如中央時間或CT。 “非定位”意味着它不會識别與UTC的偏移量。 它指的是牆上的時間——牆壁上的時鐘顯示的時間。 例如,中央時間上午8時,2017年3月1日将有UTC-06的偏移量,而2017年3月19日的UTC-05偏移量。通用非定位區域名稱不指定時區偏移量。 可以使用兩種格式-v和vvvv來分别以短格式(例如CT)和長格式(例如中央時間)生成通用非定位區域名稱。 以下代碼片段顯示了由V,Z和V格式化符号産生的格式化結果的差異:

ZonedDateTime zdt = ZonedDateTime.now();
System.out.println("Current ZonedDateTime: " + zdt);               
System.out.println("Using VV: " +
                zdt.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm VV")));
System.out.println("Using z: " +
                zdt.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm z")));
System.out.println("Using zzzz: " +
                zdt.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm zzzz")));
System.out.println("Using v: " +
                zdt.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm v")));
System.out.println("Using vvvv: " +
                zdt.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm vvvv")));
           
Current ZonedDateTime: 2017-02-12T12:30:08.975373900-06:00[America/Chicago]
Using VV: 02/12/2017 12:30 America/Chicago
Using z: 02/12/2017 12:30 CST
Using zzzz: 02/12/2017 12:30 Central Standard Time
Using v: 02/12/2017 12:30 CT
Using vvvv: 02/12/2017 12:30 Central Time
           

十三. 使用Scanner進行流操作

JDK 9将以下三個方法添加到

java.util.Scanner

中。 每個方法傳回一個

Stream

Stream<MatchResult> findAll(String patternString)
Stream<MatchResult> findAll(Pattern pattern)
Stream<String> tokens()
           

findAll()

方法傳回具有所有比對結果的流。 調用

findAll(patternString)

相當于調用

findAll(Pattern.compile(patternString))

tokens()

方法使用目前的分隔符從

scanner

傳回令牌流。下面包含一個程式,顯示如何僅使用

findAll()

方法從字元串中收集單詞。

// ScannerTest.java
package com.jdojo.misc;
import java.util.List;
import java.util.Scanner;
import java.util.regex.MatchResult;
import static java.util.stream.Collectors.toList;
public class ScannerTest {
    public static void main(String[] args) {
        String patternString = "\\b\\w+\\b";
        String input = "A test string,\n which contains a new line.";
        List<String> words = new Scanner(input)
                .findAll(patternString)
                .map(MatchResult::group)
                .collect(toList());
        System.out.println("Input: " + input);
        System.out.println("Words: " + words);
    }
}
           
Input: A test string,
 which contains a new line.
Words: [A, test, string, which, contains, a, new, line]
           

十四. Matcher類的增強

java.util.regex.Matcher

類在JDK 9中添加了一些新的方法:

Matcher appendReplacement(StringBuilder sb,  String replacement)
StringBuilder appendTail(StringBuilder sb)
String replaceAll(Function<MatchResult,String> replacer)
String replaceFirst(Function<MatchResult,String> replacer)
Stream<MatchResult> results()
           

Matcher

類在此清單中已經有前四個方法。 在JDK 9中,它們已經重載了。

appendReplacement()

appendTail()

方法用于使用

StringBuffer

。 現在他們也可以使用

StringBuilder

replaceAll()

replaceFirst()

String

作為參數。 在JDK 9中,它們已經被重載,以

Function<T,R>

作為參數。

results()

方法傳回其元素為

MatchResult

類型的流中的比對結果。 可以查詢

MatchResult

擷取結果作為字元串。 可以将

Matcher

的結果作為JDK 8中的流進行處理。但是邏輯并不簡單。

results()

方法不會重置

matcher

。 如果要重置

matcher

,不要忘記調用其

reset()

方法将其重置為所需的位置。下面顯示了這種方法的一些有趣的用法。

// MatcherTest.java
package com.jdojo.misc;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
public class MatcherTest {
    public static void main(String[] args) {
        // A regex to match 7-digit or 10-digit phone numbers
        String regex = "\\b(\\d{3})?(\\d{3})(\\d{4})\\b";
        // An input string
        String input = "1, 3342229999, 2330001, 6159996666, 123, 3340909090";
        // Create a matcher
        Matcher matcher = Pattern.compile(regex)
                                  .matcher(input);
        // Collect formatted phone numbers into a list
        List<String> phones = matcher.results()
                          .map(mr -> (mr.group(1) == null ? "" : "(" + mr.group(1) + ") ")
                                      + mr.group(2) + "-" + mr.group(3))
                          .collect(toList());
        System.out.println("Phones: " + phones);
        // Reset the matcher, so we can reuse it from start
        matcher.reset();
        // Get distinct area codes
        Set<String> areaCodes = matcher.results()
                                       .filter(mr -> mr.group(1) != null)
                                       .map(mr -> mr.group(1))
                                       .collect(toSet());
        System.out.println("Distinct Area Codes:: " + areaCodes);                
    }
}
           

輸出的結果為:

Phones: [(334) 222-9999, 233-0001, (615) 999-6666, (334) 090-9090]
Distinct Area Codes:: [334, 615]
           

main()

方法聲明兩個名

regex

input

的局部變量。 正規表達式變量包含一個正規表達式,以比對7位數或10位數字。 将使用它在輸入字元串中查找電話号碼。

input

變量儲存有嵌入電話号碼的文本。

// A regex to match 7-digit or 10-digit phone numbers
String regex = "\\b(\\d{3})?(\\d{3})(\\d{4})\\b";
// An input string
String input = "1, 3342229999, 2330001, 6159996666, 123, 3340909090";
           

接下來,将正規表達式編譯為

Pattern

對象并擷取

matcher

// Create a matcher
Matcher matcher = Pattern.compile(regex)
                         .matcher(input);
           

要将10位電話号碼格式化為(nnn)nnn-nnnn和7位數電話号碼為nnn-nnnn的格式。 最後,要将所有格式化的電話号碼收集到

List<String>

中。 以下語句執行:

// Collect formatted phone numbers into a list
 List<String> phones = matcher.results()
                              .map(mr -> (mr.group(1) == null ? "" : "(" + mr.group(1) + ") ")
                                      + mr.group(2) + "-" + mr.group(3))
                              .collect(toList());
           

請注意使用接收

MatchResult

map()

方法,并将格式化的電話号碼傳回為

String

。當一個比對是一個7位數的電話号碼時,組1将為空現在,要重新使用

matcher

, 以10位數的電話号碼查找不同的區号。必須重置

matcher

,是以下一個比對從輸入字元串的開始處開始:

// Reset the matcher, so we can reuse it from start
matcher.reset();
           

MatchResult

中的第一個組包含區号。 需要濾除7位數的電話号碼,并在

Set <String>

中收集組1的值,以獲得一組不同的區号。 以下語句是這樣做的:

// Get distinct area codes
Set<String> areaCodes = matcher.results()
                               .filter(mr -> mr.group(1) != null)
                               .map(mr -> mr.group(1))
                               .collect(toSet());
           

十五. Object類的增強

java.util.Objects

類包含對對象進行操作的靜态實用方法。 通常,它們用于驗證方法的參數,例如,檢查方法的參數是否為空。 JDK 9将以下靜态方法添加到此類中:

<T> T requireNonNullElse(T obj, T defaultObj)
<T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier)
int checkFromIndexSize(int fromIndex, int size, int length)
int checkFromToIndex(int fromIndex, int toIndex, int length)
int checkIndex(int index, int length)
           

JDK 8已經有了三個

requireNonNull()

重載方法。 該方法用于檢查值為非空值。 如果值為null,則會抛出

NullPointerException

。 JDK 9添加了這個方法的兩個版本。

如果

obj

為非空,則

requireNonNullElse(T obj, T defaultObj)

方法傳回

obj

。 如果

obj

為空,并且

defaultObj

為非空,則傳回

defaultObj

obj

defaultObj

都為空,則會抛出

NullPointerException

requireNonNullElseGet(T obj, Supplier<? extends T> supplier)

方法的工作方式與

requireNonNullElse(T obj, T defaultObj)

方法相同,前者使用

Supplier

擷取預設值。 如果非空,它傳回

obj

Supplier

非空,并傳回非空值,則傳回從

Supplier

傳回的值。 否則,抛出

NullPointerException

checkXxx()

的方法意在用于檢查索引或子範圍是否在某一範圍内。當使用數組和集合時,它們很有用,需要處理索引和子範圍。如果索引或子範圍超出範圍,這些方法将抛出

IndexOutOfBoundsException

checkFromIndexSize(int fromIndex,int size,int length)

方法檢查指定的子範圍,從

inIndex

(包括)到

fromIndex + size

(不包括)是否在範圍内,範圍是從0(含)到

length

。如果任何參數為負整數或子範圍超出範圍,則抛出

IndexOutOfBoundsException

。如果子範圍在範圍内,則傳回

fromIndex

。假設有一個接受索引和大小的方法,并從數組或清單傳回一個子範圍。可以使用此方法來檢查所請求的子範圍是否在數組或清單的範圍内。

checkFromToIndex(int fromIndex, int toIndex, int length)

inIndex

toIndex

(不包含)是否在範圍内,範圍為為0(含)到

length

(不包含)。如果任何參數是負整數或子範圍超出範圍,則抛出

IndexOutOfBoundsException

fromIndex

。在使用數組和

List

時用于子範圍檢查是非常有用的。

checkIndex(int index, int length)

方法檢查指定的索引是否在範圍内,為0(含)到

length

(不包含)。如果任何參數為負整數或索引超出範圍,則抛出

IndexOutOfBoundsException

。如果

index

在範圍内,則傳回索引。當方法接收到索引并傳回數組中的值或該索引的

List

時,它很有用。

十六. 數組比較

java.util.Arrays

類由靜态實用方法組成,可用于對數組執行各種操作,例如排序,比較,轉換為流等。在JDK 9中,此類已經獲得了幾種方法,可以比較數組和切片(slices)。 新方法分為三類:

  • 比較兩個數組或它們的切片是否相等性
  • 按字典順序比較兩個數組
  • 查找兩個數組中的第一個不比對的索引

添加到此類的方法清單是很大的。 每個類别中的方法對于所有原始類型和對象數組都是重載的。 有關完整清單,請參閱Arrays類的API文檔。

equals()

方法可以比較兩個數組的相等性。 如果數組或部分數組中的元素數量相同,并且數組或部分數組中所有對應的元素對相等,則兩個數組被認為是相等的。 以下是

int

的兩個版本的

equals()

boolean equals(int[] a, int[] b)
boolean equals(int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex)
           

第一個版本允許比較兩個數組之間的相等性,并且存在于JDK 9之前。第二個版本允許将兩個數組的部分進行比較,以便在JDK 9中添加相等。

fromIndex

(包含)和

toIndex

(不包含)參數決定要比較的兩個數組的範圍。 如果兩個數組相等,則該方法傳回true,否則傳回false。 如果兩個數組都為空,則認為兩個數組相等。

JDK 9添加了幾個

compare()

compareUnsigned()

的方法。 這兩種方法都按字典順序比較數組或部分數組中的元素。

compareUnsigned()

方法将整數值視為無符号。 空數組的字元拼寫小于非空數組。 兩個空數組相等。 以下是對于

int

compare()

方法的兩個版本:

int compare(int[] a, int[] b)
int compare(int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex)
           

如果第一個和第二個數組相等并且包含相同的元素,

compare()

方法傳回0; 如果第一個數組在字典上小于第二個數組,則傳回小于0的值; 并且如果第一個數組在字典上大于第二個數組則傳回大于0的值。

mismatch()

方法比較兩個數組或數組的一部分。 以下是

int

mismatch()

int mismatch(int[] a, int[] b)
int mismatch (int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex)
           

mismatch()

方法傳回第一個不比對的索引。 如果沒有不比對,則傳回-1。 如果任一數組為空,則抛出

NullPointerException

。 下包含一個比較兩個數組及其部分數組的完整程式。 該程式使用兩個

int

數組。

// ArrayComparision.java
package com.jdojo.misc;
import java.util.Arrays;
public class ArrayComparison {
    public static void main(String[] args) {
        int[] a1 = {1, 2, 3, 4, 5};
        int[] a2 = {1, 2, 7, 4, 5};
        int[] a3 = {1, 2, 3, 4, 5};
        // Print original arrays
        System.out.println("Three arrays:");
        System.out.println("a1: " + Arrays.toString(a1));
        System.out.println("a2: " + Arrays.toString(a2));
        System.out.println("a3: " + Arrays.toString(a3));
        // Compare arrays for equality
        System.out.println("\nComparing arrays using equals() method:");
        System.out.println("Arrays.equals(a1, a2): " + Arrays.equals(a1, a2));
        System.out.println("Arrays.equals(a1, a3): " + Arrays.equals(a1, a3));
        System.out.println("Arrays.equals(a1, 0, 2, a2, 0, 2): " +
                           Arrays.equals(a1, 0, 2, a2, 0, 2));
        // Compare arrays lexicographically
        System.out.println("\nComparing arrays using compare() method:");
        System.out.println("Arrays.compare(a1, a2): " + Arrays.compare(a1, a2));
        System.out.println("Arrays.compare(a2, a1): " + Arrays.compare(a2, a1));
        System.out.println("Arrays.compare(a1, a3): " + Arrays.compare(a1, a3));
        System.out.println("Arrays.compare(a1, 0, 2, a2, 0, 2): " +
                           Arrays.compare(a1, 0, 2, a2, 0, 2));
        // Find the mismatched index in arrays
        System.out.println("\nFinding mismatch using the mismatch() method:");                
        System.out.println("Arrays.mismatch(a1, a2): " + Arrays.mismatch(a1, a2));
        System.out.println("Arrays.mismatch(a1, a3): " + Arrays.mismatch(a1, a3));
        System.out.println("Arrays.mismatch(a1, 0, 5, a2, 0, 1): " +
                            Arrays.mismatch(a1, 0, 5, a2, 0, 1));
    }
}
           
a1: [1, 2, 3, 4, 5]
a2: [1, 2, 7, 4, 5]
a3: [1, 2, 3, 4, 5]
Comparing arrays using equals() method:
Arrays.equals(a1, a2): false
Arrays.equals(a1, a3): true
Arrays.equals(a1, 0, 2, a2, 0, 2): true
Comparing arrays using compare() method:
Arrays.compare(a1, a2): -1
Arrays.compare(a2, a1): 1
Arrays.compare(a1, a3): 0
Arrays.compare(a1, 0, 2, a2, 0, 2): 0
Finding mismatch using the mismatch() method:
Arrays.mismatch(a1, a2): 2
Arrays.mismatch(a1, a3): -1
Arrays.mismatch(a1, 0, 5, a2, 0, 1): 1
           

十七. Applet API已經廢棄

Java applets需要Java浏覽器插件才能正常工作。 許多浏覽器供應商已經删除了對Java浏覽器插件的支援,或者将在不久的将來删除它。 如果浏覽器不支援Java插件,則不能使用applet,是以沒有理由使用Applet API。 JDK 9棄用了Applet API。 但是,它将不會在JDK 10中被删除。如果計劃在将來的版本中被删除,開發人員将提前釋出一個通知。 以下類和接口已被棄用:

java.applet.AppletStub
java.applet.Applet
java.applet.AudioClip
java.applet.AppletContext
javax.swing.JApplet
           

在JDK 9中,所有AWT和Swing相關類都打包在java.desktop子產品中。 這些不推薦的類和接口也在同一個子產品中。

appletviewer工具随其JDK在bin目錄中提供,用于測試applet。 該工具也在JDK 9中不推薦使用。在JDK 9中運作該工具會列印一個棄用警告。

十八. Javadoc增強

JDK 9引入了Javadoc的編寫,生成和使用方式的一些增強功能。 JDK 9在Javadoc中支援HTML5。 預設情況下,javadoc工具仍然在HTML4中生成輸出。 一個新的選項

-html5

已添加到該工具中,表明希望HTML5中的輸出:

javadoc -html5 <other-options>
           

javadoc工具位于JDK_HOME\bin目錄中。 使用

--hel

p選項運作工具列印其使用說明和所有選項。

NetBeans IDE可以為項目生成Javadoc。 在項目的“屬性”對話框中,選擇“Build”➤“Documenting”以擷取Javadoc屬性頁,可以在其中指定javadoc工具的所有選項。 要生成Javadoc,請從項目的右鍵菜單選項中選擇“Generate Javadoc”。

JDK 9保留了三個Frame或無Frame的Javadoc布局。 左上角的架構包含三個連結:所有類,所有包和所有子產品。 在JDK 9中添加了ALL MODULES連結,其中顯示了所有子產品的清單。 ALL CLASSES連結可以檢視左下架構中的所有類。 其他兩個連結可檢視所有軟體包,子產品中的所有軟體包以及所有子產品。 下面顯示了Javadoc頁面的更改。

考慮這種情況。 正在尋找在Java中實作某些内容的邏輯,并在Internet上找到一段代碼,該代碼使用類,但不顯示導入該類的導入語句。 可以通路Java SE的Javadoc,并希望了解更多關于該類的資訊。 如何擷取類的包名,這是需要擷取類的文檔? 再次搜尋網際網路。 這次,搜尋類名,這可能會獲得該類的Javadoc的連結。 或者,可以将這段代碼複制并粘貼到Java IDE(如NetBeans和Eclipse)中,IDE将生成導入語句,提供類的包名稱。 不要擔心在JDK 9中搜尋類的包名稱的這種不便。

右邊的主Frame還有另一個補充。 此框中的所有頁面都顯示右上角的“搜尋”框。 搜尋框可搜尋Javadoc。 javadoc工具準備可搜尋的術語索引。 要知道可搜尋的内容,需要知道索引的條款:

  • 可以搜尋子產品,軟體包,類型和成員的聲明名稱。 構造方法和方法的形式參數的類型被索引,但不是這些參數的名稱。 是以,可以搜尋形式參數的類型。 如果在搜尋框中輸入“(String, int, int)”,将會找到使用String,int和int三個形式參數的構造函數和方法的清單。 如果輸入“util”作為搜尋項,它将顯示包含名稱中的“util”一詞的所有包,類型和成員的清單。
  • JDK 9引入了一個新的内聯Javadoc标簽

    @index

    ,可以用來告訴javadoc工具對關鍵字進行索引。 它可以作為

    {@index <keyword> <description>}

    出現在Javadoc中,其中

    <keyword>

    是要被索引的關鍵字,而

    <description>

    是關鍵字的描述。 以下Javadoc标記是使用帶有關鍵字jdojo的

    @index

    标記的示例:

    jdojo: {@index jdojo Info site ( [www.jdojo.com ](http://www.jdojo.com/)) for the Java 9 Revealed book!}

此清單中未列出的其他所有内容都不可使用Javadoc搜尋框進行搜尋。 當輸入搜尋字詞時,搜尋框會将搜尋結果顯示為清單。 結果清單分為類别,如子產品,包,類型,成員和搜尋标簽。 SearchTags類别包含從使用

@index

标記指定的索引關鍵字中找到的結果。

Javadoc搜尋不支援正規表達式。

下圖顯示了使用結果清單的Javadoc搜尋框。 為com.jdojo.misc子產品生成了Javadoc,并使用它來搜尋jdojo。 使用Java SE 9 的Javadoc來搜尋術語Module,如右圖所示。

可以使用向上和向下箭頭鍵浏覽搜尋結果。 可以通過以下兩種方式檢視搜尋結果的詳細資訊:

  • 單擊搜尋結果以打開該主題的Javadoc。
  • 當使用向上/向下箭頭突出顯示搜尋結果時,按Enter打開該主題的詳細資訊。
可以使用

-noindex

選項與

javadoc

工具來禁用Javadoc搜尋。 将不會生成索引,并且生成的Javadoc中不會有搜尋框可用。

使用用戶端JavaScript本地執行Javadoc搜尋。 在伺服器中沒有實作計算或搜尋邏輯。 如果在浏覽器中禁用JavaScript,則無法使用Javadoc搜尋功能。

十九. 本地桌面功能

Java SE 6通過

java.awt.Desktop

類添加了特定于平台的桌面支援。 該類支援從Java應用程式執行以下操作:

  • 在使用者預設浏覽器中打開URI
  • 在使用者預設郵件用戶端中打開mailto URI
  • 使用注冊的應用程式打開,編輯和列印檔案

如果Java SE 9在目前平台上可用,那麼Java SE 9推出面向特定于平台的桌面支援,并為許多系統和應用程式事件通知添加公共API支援。

java.awt.Desktop

類仍然是使用平台特定的桌面功能的中心類。 為了支援這麼多新的桌面功能,Java SE 9向java.desktop子產品添加了一個新的包java.awt.desktop。

java.awt.Desktop

類也有很多新方法。 新包包含30個類和接口。 在JDK 9中,Desktop API支援24個特定于桌面的桌面操作和通知,它們由

Desktop.Action

枚舉的常量定義。 舉幾個例子,它們如下:

  • 當附件顯示進入或退出節電時的通知
  • 當系統進入睡眠或系統喚醒後的通知
  • 使用者會話更改時的通知,例如鎖定/解鎖使用者會話
  • 當應用程式的狀态更改為或不是前台應用程式時的通知
  • 要求應用程式顯示其“關于”對話框時的通知

可以使用這些功能來優化應用程式的資源使用情況。 例如,如果系統進入睡眠模式,您可以停止動畫,并在系統喚醒時恢複。 有關詳細資訊,請參閱

java.awt.Desktop類

的API文檔以及java.awt.desktop包中的類和接口。 使用桌面功能時,以下是典型的步驟:

  • 使用此類的靜态

    isDesktopSupported()

    方法檢查目前平台是否支援

    Desktop

    類。 如果該方法傳回false,則不能使用任何桌面功能。
  • 如果支援

    Desktop

    類,請使用

    Desktop

    類的靜态

    getDesktop()

    方法擷取

    Desktop

    類的引用。
  • 并非所有桌面功能都可在所有平台上使用。 在桌面對象上使用

    isSupported(Desktop.Action action)

    方法來檢查是否支援特定的桌面操作。 受支援的桌面操作由

    Desktop.Action

    枚舉中的常量表示。
  • 如果支援桌面操作,可以調用

    Desktop

    類的一個方法來執行諸如打開檔案的操作,也可以使用

    addAppEventListener(SystemEventListener listener)

    方法注冊事件處理程式。
java.awt和java.awt.desktop包在java.desktop子產品中。 當使用平台特定的桌面功能時,請確定你的子產品讀取java.desktop子產品。

下面包含一個示範桌面功能的完整程式。 應用程式注冊使用者會話更改監聽器。 當使用者會話更改時,通知應用程式,并在标準輸出上列印消息。 可以通過遠端登入/登出或通過鎖定和解鎖計算機來更改使用者會話。 可以使用此桌面通知來暫停昂貴的處理,例如使用者會話被停用時的動畫,并在激活該過程時重新啟動該過程。 以下詳細說明本程式的輸出。 運作程式時,需要鎖定和解鎖您的計算機與使用者會話相關的輸出。 顯示的輸出是在Windows上運作這個程式,并在程式運作時鎖定和解鎖我的計算機一次。 你可能得到不同的輸出。 兩分鐘後,程式自行退出。

// DeskTopFrame.java
package com.jdojo.misc;
import java.awt.Desktop;
import java.awt.desktop.UserSessionEvent;
import java.awt.desktop.UserSessionListener;
import java.util.concurrent.TimeUnit;
public class DeskTopFrame {
    public static void main(String[] args) {
        // Check if Desktop class is available
        if (!Desktop.isDesktopSupported()) {
            System.out.println("Current Platform does not support Desktop.");
            return;
        }
        System.out.println("Current platform supports Desktop.");
        // Get the desktop reference
        Desktop desktop = Desktop.getDesktop();
        // Check if user session event notification is supported
        if (!desktop.isSupported(Desktop.Action.APP_EVENT_USER_SESSION)) {
            System.out.println("User session notification is not " +
                               "supported by the current desktop");
            return;
        }
        System.out.println("Lock and unlock your session to see " +
                           "user session change notification in action.");
        // Add an event handler for a change in user session
        desktop.addAppEventListener(new UserSessionListener() {
            @Override
            public void userSessionDeactivated(UserSessionEvent e) {
                System.out.println("User session deactivated. Reason: " + e.getReason());
            }
            @Override
            public void userSessionActivated(UserSessionEvent e) {
                System.out.println("User session activated. Reason: " + e.getReason());
            }
        });
        // Make the current thread sleep for 2 minutes
        try {            
            TimeUnit.SECONDS.sleep(120);            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
           
Current platform supports Desktop.
Lock and unlock your session to see user session change notification in action.
User session deactivated. Reason: LOCK
User session activated. Reason: LOCK
           

main()

方法檢查目前平台上的

Desktop

類是否可用。 如果不可用,程式退出。 如果可用,則獲得其引用。

if (!Desktop.isDesktopSupported()) {
    System.out.println("Current Platform does not support Desktop.");
    return;
}
// Get the desktop reference
Desktop desktop = Desktop.getDesktop();
           

如有興趣在使用者會話更改時收到通知,是以需要檢查此功能是否受支援。 如果不支援,程式退出。

// Check if user session event notification is supported
if (!desktop.isSupported(Desktop.Action.APP_EVENT_USER_SESSION)) {
    System.out.println("User session notification is not " +
                       "supported by the current desktop");
     return;
}
           

如果支援使用者會話更改通知,則需要注冊

UserSessionListener

類型的事件監聽器,如下所示:

// Add an event handler for a change in user session
desktop.addAppEventListener(new UserSessionListener() {
    @Override
    public void userSessionActivated(UserSessionEvent e) {
        System.out.println("Use session activated. Reason: " + e.getReason());
    }
    @Override
    public void userSessionDeactivated(UserSessionEvent e) {
        System.out.println("User session deactivated. Reason: " + e.getReason());
    }
});
           

分别激活和停用使用者會話時,會調用注冊的

UserSessionListener

userSessionActivated()

userSessionDeactivated()

方法。 兩個方法都将一個

UserSessionEvent

對象作為參數。

UserSessionEvent

getReason()

方法傳回一個

UserSessionEvent.Reason

,它是一個枚舉,它的常量定義了使用者會話更改的原因。 枚舉有四個常量:

CONSOLE

LOCK

REMOTE

UNSPECIFIED

CONSOLE

REMOTE

常數表示使用者會話分别與控制台終端和遠端終端連接配接/斷開的原因。

LOCK

常數表示訓示使用者會話已被鎖定或解鎖的原因。 顧名思義,

UNSPECIFIED

常數表示使用者會話更改的所有其他原因。

最後,

main()

方法使目前線程休眠兩分鐘,是以有機會鎖定和解鎖會話以檢視程式的工作。 如果删除程式的這一部分,程式将退出,而不等待更改使用者會話。

二十. 對象反序列化過濾器

Java可以對對象進行序列化和反序列化。 為了解決反序列化帶來的安全風險,JDK 9引入了可以用來驗證反序列化對象的對象輸入過濾器的概念,如果不通過測試,則可以停止反序列化過程。 對象輸入過濾器是添加到JDK 9的新接口

java.io.ObjectInputFilter

的執行個體。過濾器可以基于以下一個或多個條件:

  • 數組的長度反序列化
  • 嵌套對象的深度反序列化
  • 對象引用數反序列化
  • 對象的類被反序列化
  • 從輸入流消耗的位元組數

ObjectInputFilter

接口隻包含一個方法:

ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo)
           

可以指定要用于反序列化所有對象的全局過濾器。 可以通過為對象輸入流設定本地過濾器來重寫每個

ObjectInputStream

上的全局過濾器。 可以沒有全局過濾器,并為每個對象輸入流指定本地過濾器。 有幾種方法來建立和指定過濾器。 本節首先介紹添加到JDK 9中的類和接口,需要使用這些類和接口來處理過濾器:

ObjectInputFilter
ObjectInputFilter.Config
ObjectInputFilter.FilterInfo
ObjectInputFilter.Status
           

ObjectInputFilter

接口的執行個體表示過濾器。 可以通過在類中實作此接口來建立過濾器。 或者,可以使用

ObjectInputFilter.Config

createFilter(String pattern)

方法從字元串擷取其執行個體。

ObjectInputFilter.Config

是一個嵌套的靜态實用類,用于兩個目的:

  • 擷取并設定全局過濾器
  • 從指定字元串的模式中建立過濾器

ObjectInputFilter.Config

類包含以下三種靜态方法:

ObjectInputFilter createFilter(String pattern)
ObjectInputFilter getSerialFilter()
void setSerialFilter(ObjectInputFilter filter)
           

createFilter()

方法接受一個描述過濾器的模式,并傳回

ObjectInputFilter

接口的執行個體。 以下代碼片段建立一個過濾器,指定反序列化數組的長度不應超過4:

String pattern = "maxarray=4";
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
           

可以在一個過濾器中指定多個模式。 它們用分号(;)分隔。 以下代碼片段從兩種模式建立一個過濾器。 如果遇到長度大于4的數組或串行化對象的大小大于1024位元組,則過濾器将拒絕對象反序列化。

String pattern = "maxarray=4;maxbytes=1024";
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
           

指定過濾器模式有幾個規則。 如果喜歡在Java代碼中編寫過濾器邏輯,可以通過建立實作

ObjectInputFilter

接口的類并将其寫入其

checkInput()

方法來實作。 如果要從字元串中的模式建立過濾器,請遵循以下規則:

有五個過濾條件,其中四個是限制。 它們是

maxarray

maxdepth

maxrefs

maxbytes

。 可以使用

name = value

來設定它們,其中

name

是這些關鍵字,

value

是限制。 如果模式包含等号(=),則模式必須使用這四個關鍵字作為名稱。 第五個過濾條件用于指定類名形式的模式:

<module-name>/<fully-qualified-class-name>
           
  • 如果一個類是未命名的子產品,則該模式将與類名比對。 如果對象是一個數組,則數組的元件類型的類名用于比對模式,而不是數組本身的類名。 以下是比對類名稱的模式的所有規則:
  • 如果類名與模式比對,則允許對象反序列化。
  • 以“!” 模式開頭的字元被視為邏輯NOT。
  • 如果模式包含斜杠(/),斜杠之前的部分是子產品名稱。 如果子產品名稱與類的子產品名稱相比對,則斜線後面的部分将被用作比對類名稱的模式。 如果模式中沒有斜線,則在比對模式時不考慮類的子產品名稱。
  • 以“.**”結尾的模式比對包中的任何類和所有子軟體包。
  • 以“.*”結尾的模式比對包中的任何類。
  • 以“*”結尾的模式比對任何具有模式作為字首的類。
  • 如果模式等于類名稱,則它比對。
  • 另外,模式不比對,對象被拒絕。

如果将

com.jdojo.**

設定為過濾器模式,它允許com.jdojo包中的所有類及其子包都被反序列化,并将拒絕所有其他類的反序列化對象。 如果将“com.jdojo.**”設定為過濾器模式,它将拒絕com.jdojo包中的所有類及其子包以進行反序列化,并允許反序列化所有其他類的對象。

getSerialFilter()

setSerialFilter()

方法用于擷取和設定全局過濾器。 可以使用以下三種方式之一設定全局過濾器:

  • 通過設定名為

    jdk.serialFilter

    的系統屬性,該屬性的值是以分号分隔的一系列過濾器模式。
  • 通過在java.security檔案中設定一個存儲在JAVA_HOME\conf\security目錄中的

    jdk.serialFilter

    屬性。 如果正在使用JDK運作程式,請将JAVA_HOME作為JDK_HOME讀取。 否則,将其讀為JRE_HOME。
  • 通過調用

    ObjectInputFilter.Config

    setSerialFilter()

    靜态方法。

以下指令在運作類時将

jdk.series

屬性設定為指令行選項。 不要擔心這個指令的其他細節。

C:\Java9Revealed>java -Djdk.serialFilter=maxarray=100;maxdepth=3;com.jdojo.** --module-path com.jdojo.misc\build\classes --module com.jdojo.misc/com.jdojo.misc.ObjectFilterTest
           

下面顯示了JAVA_HOME\conf\security\java.security配置檔案的部分内容。 該檔案包含更多的條目。 隻顯示一個設定過濾器的條目,這與設定jdk.serialFilter系統屬性具有相同的效果,如上一個指令所示。

maxarray=100;maxdepth=3;com.jdojo.**
           
如果在系統屬性和配置檔案中設定過濾器,則優先使用系統屬性中的值。

當運作具有全局過濾器的java指令時,會注意到stderr上的消息類似于此處顯示的消息:

Feb 17, 2017 9:23:45 AM java.io.ObjectInputFilter$Config lambda$static$0
INFO: Creating serialization filter from maxarray=20;maxdepth=3;!com.jdojo.**
           

這些消息使用java.io.serialization的Logger作為平台消息記錄java.base子產品。 如果指定了平台Logger,這些消息将被記錄到Logger中。 其中一條消息在系統屬性或配置檔案中列印全局過濾器集。

還可以使用

ObjectInputFilter.Config

setSerialFilter()

方法在代碼中設定全局過濾器:

// Create a filter
String pattern = "maxarray=100;maxdepth=3;com.jdojo.**";
ObjectInputFilter globalFilter = ObjectInputFilter.Config.createFilter(pattern);
// Set a global filter
ObjectInputFilter.Config.setSerialFilter(globalFilter);
           
隻能設定一次全局過濾器。 例如,如果使用jdk.serialFilter系統屬性設定過濾器,則在代碼中調用

Config.setSerialFiter()

将抛出

IllegalStateException

。 當使用`Config.setSerialFiter()方法設定全局過濾器時,必須設定非空值過濾器。 存在這些規則,以確定在代碼中無法覆寫使用系統屬性或配置檔案的全局過濾器集。

ObjectInputFilter.Config

getSerialFilter()

方法擷取全局過濾器,而不考慮過濾器的設定方式。 如果沒有全局過濾器,則此方法傳回null。

ObjectInputFilter.FilterInfo

是一個嵌套的靜态接口,其執行個體包裝了反序列化的目前上下文。

ObjectInputFilter.FilterInfo

的執行個體被建立并傳遞給過濾器的

checkInput()

方法。 不必在程式中實作此接口并建立其執行個體。 該接口包含以下方法,将在自定義過濾器的

checkInput()

方法中使用以讀取目前反序列化上下文:

Class<?> serialClass()
long arrayLength()
long depth();
long references();
long streamBytes();
           

serialClass()

方法傳回反序列化對象的類。對于數組,它傳回數組的類,而不是數組的元件類型的類。在反序列化期間未建立新對象時,此方法傳回null。

arrayLength()

方法傳回反序列化數組的長度。它被反序列化的對象不是數組,它傳回-1。

depth()

方法傳回被反序列化的對象的嵌套深度。它從1開始,對于每個嵌套級别遞增1,當嵌套對象傳回時,遞減1。

references()

方法傳回反序列化的對象引用的目前數量。

streamBytes()

方法傳回從對象輸入流消耗的目前位元組數。

對象可能根據指定的過濾條件會通過,也可能會失敗。根據測試結果,應該傳回

ObjectInputFilter.Status

枚舉的以下常量。通常,在自定義過濾器類的

checkInput()

方法中使用這些常量作為傳回值。

ALLOWED
REJECTED
UNDECIDED
           

這些常量表示反序列化允許,拒絕和未定。 通常,傳回

UNDECIDED

表示一些其他過濾器将決定目前對象的反序列化是否繼續。 如果正在建立一個過濾器以将類列入黑名單,則可以傳回

REJECTED

以擷取黑名單類别的比對項,而對其他類别則為

UNDECIDED

下面包含一個基于數組長度進行過濾的簡單過濾器。

// ArrayLengthObjectFilter.java
package com.jdojo.misc;
import java.io.ObjectInputFilter;
public class ArrayLengthObjectFilter implements ObjectInputFilter {
    private long maxLenth = -1;
    public ArrayLengthObjectFilter(int maxLength) {
        this.maxLenth = maxLength;
    }
    @Override
    public Status checkInput(FilterInfo info) {
        long arrayLength = info.arrayLength();
        if (arrayLength >= 0 && arrayLength > this.maxLenth) {
            return Status.REJECTED;
        }
        return Status.ALLOWED;
    }
}
           

以下代碼片段通過将數組的最大長度指定為3來使用自定義過濾器。如果對象輸入流包含長度大于3的數組,則反序列化将失敗,并顯示

java.io.InvalidClassException

。 代碼不顯示異常處理邏輯。

ArrayLengthObjectFilter filter = new ArrayLengthObjectFilter(3);
File inputFile = ...
ObjectInputStream in =  new ObjectInputStream(new FileInputStream(inputFile))) {            
in.setObjectInputFilter(filter);
Object obj = in.readObject();
           

下面包含一個

Item

類的代碼。為保持代碼簡潔,省略了getter和setter方法。 使用它的對象來示範反序列化過濾器。

// Item.java
package com.jdojo.misc;
import java.io.Serializable;
import java.util.Arrays;
public class Item implements Serializable {
    private int id;    
    private String name;
    private int[] points;
    public Item(int id, String name, int[] points) {
        this.id = id;
        this.name = name;
        this.points = points;
    }
    /* Add getters and setters here */
    @Override
    public String toString() {
        return "[id=" + id + ", name=" + name + ", points=" + Arrays.toString(points) + "]";
    }
}
           

下面包含

ObjectFilterTest

類的代碼,用于示範在對象反序列化過程中使用過濾器。 代碼中有詳細的說明。

// ObjectFilterTest.java
package com.jdojo.misc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputFilter;
import java.io.ObjectInputFilter.Config;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectFilterTest {
    public static void main(String[] args)  {         
        // Relative path of the output/input file
        File file = new File("serialized", "item.ser");
        // Make sure directories exist
        ensureParentDirExists(file);
        // Create an Item used in serialization and deserialization
        Item item = new Item(100, "Pen", new int[]{1,2,3,4});
        // Serialize the item
        serialize(file, item);
        // Print the global filter
        ObjectInputFilter globalFilter = Config.getSerialFilter();
        System.out.println("Global filter: " + globalFilter);
        // Deserialize the item
        Item item2 = deserialize(file);
        System.out.println("Deserialized using global filter: " + item2);
        // Use a filter to reject array size > 2
        String maxArrayFilterPattern = "maxarray=2";
        ObjectInputFilter maxArrayFilter = Config.createFilter(maxArrayFilterPattern);         
        Item item3 = deserialize(file, maxArrayFilter);
        System.out.println("Deserialized with a maxarray=2 filter: " + item3);
        // Create a custom filter
        ArrayLengthObjectFilter customFilter = new ArrayLengthObjectFilter(5);                
        Item item4 = deserialize(file, customFilter);
        System.out.println("Deserialized with a custom filter (maxarray=5): " + item4);
    }
    private static void serialize(File file, Item item) {        
        try (ObjectOutputStream out =  new ObjectOutputStream(new FileOutputStream(file))) {            
            out.writeObject(item);
            System.out.println("Serialized Item: " + item);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static Item deserialize(File file) {
        try (ObjectInputStream in =  new ObjectInputStream(new FileInputStream(file))) {                        
            Item item = (Item)in.readObject();
            return item;
        } catch (Exception e) {
            System.out.println("Could not deserialize item. Error: " + e.getMessage());
        }
        return null;
    }
    private static Item deserialize(File file, ObjectInputFilter filter) {
        try (ObjectInputStream in =  new ObjectInputStream(new FileInputStream(file))) {            
            // Set the object input filter passed in
            in.setObjectInputFilter(filter);
            Item item = (Item)in.readObject();
            return item;
        } catch (Exception e) {
            System.out.println("Could not deserialize item. Error: " + e.getMessage());            
        }
        return null;
    }
    private static void ensureParentDirExists(File file) {
        File parent = file.getParentFile();
        if(!parent.exists()) {
            parent.mkdirs();
        }
        System.out.println("Input/output file is " + file.getAbsolutePath());
    }
}
           

ObjectFilterTest

使用不同的過濾器序列化

Item

類,随後使用相同

Item

類多個反序列化。

ensureParentDirExists()

方法接受一個檔案,并確定其父目錄存在,如果需要建立它。 該目錄還列印序列化檔案的路徑。

serialize()

方法将指定的

Item

對象序列化為指定的檔案。 這個方法從

main()

方法調用一次序列化一個

Item

對象。

deserialize()

方法是重載的。

deserialize(File file)

版本使用全局過濾器(如果有的話)反序列化儲存在指定檔案中的

Item

deserialize(File file, ObjectInputFilter filter)

版本使用指定的過濾器反序列化儲存在指定檔案中的

Item

對象。 注意在此方法中使用

in.setObjectInputFilter(filter)

方法調用。 它為

ObjectInputStream

設定指定的過濾器。 此過濾器将覆寫全局過濾器(如果有)。

main()

方法列印全局過濾器,建立一個

Item

對象并對其進行序列化,建立多個本地過濾器,并使用不同的過濾器對同一個

Item

對象進行反序列化。 以下指令運作

ObjectFilterTest

類而不使用全局過濾器。 可能得到不同的輸出。

C:\Java9Revealed>java --module-path com.jdojo.misc\build\classes
--module com.jdojo.misc/com.jdojo.misc.ObjectFilterTest
           
Input/output file is C:\Java9Revealed\serialized\item.ser
Serialized Item: [id=100, name=Pen, points=[1, 2, 3, 4]]
Global filter: null
Deserialized using global filter: [id=100, name=Pen, points=[1, 2, 3, 4]]
Could not deserialize item. Error: filter status: REJECTED
Deserialized with a maxarray=2 filter: null
Deserialized with a custom filter (maxarray=2): [id=100, name=Pen, points=[1, 2, 3, 4]]
           

以下指令使用全局過濾器

maxarray = 1

運作

ObjectFilterTest

類,這将防止具有多個元素的數組被反序列化。 全局過濾器是使用jdk.serialFilter系統屬性設定的。 因為正在使用全局過濾器,JDK類将在stderr上記錄消息。

C:\Java9Revealed>java -Djdk.serialFilter=maxarray=1
--module-path com.jdojo.misc\build\classes
--module com.jdojo.misc/com.jdojo.misc.ObjectFilterTest
           
Input/output file is C:\Java9Revealed\serialized\item.ser
Serialized Item: [id=100, name=Pen, points=[1, 2, 3, 4]]
Feb 17, 2017 1:09:57 PM java.io.ObjectInputFilter$Config lambda$static$0
INFO: Creating serialization filter from maxarray=1
Global filter: maxarray=1
Could not deserialize item. Error: filter status: REJECTED
Deserialized using global filter: null
Could not deserialize item. Error: filter status: REJECTED
Deserialized with a maxarray=2 filter: null
Deserialized with a custom filter (maxarray=5): [id=100, name=Pen, points=[1, 2, 3, 4]]
           

注意使用全局過濾器時的輸出。 因為

Item

對象包含一個包含四個元素的數組,是以全局過濾器阻止它反序列化。 但是,可以使用

ArrayLengthObjectFilter

對同一對象進行反序列化,因為此過濾器覆寫全局過濾器,并允許數組中最多有五個元素。 這在輸出的最後一行是顯而易見的。

二十一. Java I/O API新增方法

JDK 9向I/O API添加了一些友善的方法。 第一個是

InputStream

類中的一種新方法:

long transferTo(OutputStream out) throws IOException
           

編寫的代碼從輸入流讀取所有位元組,以便寫入輸出流。 現在,不必編寫一個循環來從輸入流讀取位元組并将其寫入輸出流。

transferTo()

方法從輸入流讀取所有位元組,并将它們讀取時依次寫入指定的輸出流。 該方法傳回傳輸的位元組數。

transferTo()

方法不會關閉任何一個流。 當此方法傳回時,輸入流将在流的末尾。

忽略異常處理和流關閉邏輯,這裡是一行代碼,将log.txt檔案的内容複制到log_copy.txt檔案。

new FileInputStream("log.txt").transferTo(new FileOutputStream("log_copy.txt"));
           

java.nio.Buffer

類在JDK 9中增加了兩種新方法:

abstract Buffer duplicate()
abstract Buffer slice()
           

兩種方法傳回一個

Buffer

,它共享原始緩沖區的内容。 僅當原始緩沖區是直接的或隻讀時,傳回的緩沖區将是直接的或隻讀的。

duplicate()

方法傳回一個緩沖區,其容量,臨界,位置和标記值将與原始緩沖區的值相同。

slice()

方法傳回一個緩沖區,其位置将為零,容量和臨界是此緩沖區中剩餘的元素數量,标記不定義。 傳回的緩沖區的内容從原始緩沖區的目前位置開始。 來自這些方法的傳回緩沖區保持與原始緩沖區無關的位置,限定和标記。 以下代碼片段顯示了

duplicated

sliced

緩沖區的特征:

IntBuffer b1 = IntBuffer.wrap(new int[]{1, 2, 3, 4});
IntBuffer b2 = b1.duplicate();
IntBuffer b3 = b1.slice();
System.out.println("b1=" + b1);
System.out.println("b2=" + b2);
System.out.println("b2=" + b3);
// Move b1 y 1 pos
b1.get();
IntBuffer b4 = b1.duplicate();
IntBuffer b5 = b1.slice();
System.out.println("b1=" + b1);
System.out.println("b4=" + b4);
System.out.println("b5=" + b5);
b1=java.nio.HeapIntBuffer[pos=0 lim=4 cap=4]
b2=java.nio.HeapIntBuffer[pos=0 lim=4 cap=4]
b2=java.nio.HeapIntBuffer[pos=0 lim=4 cap=4]
b1=java.nio.HeapIntBuffer[pos=1 lim=4 cap=4]
b4=java.nio.HeapIntBuffer[pos=1 lim=4 cap=4]
b5=java.nio.HeapIntBuffer[pos=0 lim=3 cap=3]
           

二十二. 總結

在JDK 9中,下劃線(_)是一個關鍵字,不能将其本身用作單字元辨別符,例如變量名稱,方法名稱,類型名稱等。但是,仍然可以使用下劃線多個字元的辨別符名稱。

JDK 9删除了限制,必須使用try-with-resource塊為要管理的資源聲明新變量。現在,可以使用

final

final

變量來引用資源由try-with-resources塊來管理。

隻要推斷的類型是可表示的,JDK 9就添加了對匿名類中的鑽石操作符的支援。

可以在接口中具有非抽象非預設執行個體方法或靜态方法的私有方法。

JDK 9允許在私有方法上使用@SafeVarargs注解。 JDK 8已經允許它在構造方法,

stati方法和

final`方法上。

ProcessBuilder.Redirect

嵌套類添加了

DISCARD

的新常量。它的類型是

ProcessBuilder.Redirect

。當要丢棄輸出時,可以将其用作子程序的輸出和錯誤流的目标。實作通過寫入作業系統特定的“空檔案”來丢棄輸出。

JDK 9為

Math

StrictMath

類添加了幾種方法來支援更多的數學運算,如

floorDiv(long x, int y)

floorMod(long x, int y)

multiplyExact(long x, int y)

multiplyFull(int x, int y)

multiplyHigh(long x, long y)

等。

java.util.Optional

類添加了三個方法:

ifPresentOrElse()

of()

stream()

ifPresentOrElse()

方法可以提供兩個備選的操作。如果存在值,則執行一個操作。否則,它執行另一個操作。如果存在值,則

or()

Optional

。否則傳回指定

Supplier

傳回的可選項。

stream()

方法傳回包含可選中存在的值的元素的順序流。如果

Optional

為空,則傳回一個空的流。

stream()

方法在扁平映射中(flat maps)很有用。

Thread

onSpinWai()

方法。對處理器來說,這是一個純粹的提示,即調用者線程暫時無法繼續,是以可以優化資源使用。在自旋循環中使用它。

Time API在JDK 9中得到了一個提升。在

Duration

LocalDate

LocalTime

OffsetTime

類中添加了幾種方法。

LocalDate

類接收到一個新的

datesUntil()

方法,它傳回兩個日期之間的日期流,以一天或給定期間的增量。 Time API中有幾個新的格式化符号。

Matcher

類新增幾個現有方法的重載版本,它們用于與

StringBuffer

一起工作,以支援使用

StringBuilder

。一個為

results()

的新方法傳回一個

Stream<MatchResult>

Objects

類收到了幾個新的實用方法來檢查數組和集合的範圍。

ava.util.Arrays

新增了幾種方法,可以比較數組和部分數組的相等性和不比對性。

Javadoc在JDK 9中得到了增強。它支援HTML5。可以使用一個新的選項

-html5

與javadoc工具一起生成HTML5格式的Javadoc。對所有子產品,包,類型,成員和形式參數類型的名稱進行索引,并使用新的搜尋功能進行搜尋。 Javadoc在每個首頁的右上角顯示一個搜尋框,可用于搜尋索引條款。還可以在Javadoc中使用一個新的标簽

@index

來建立使用者定義的術語。使用用戶端JavaScript執行搜尋,并且不進行伺服器通信。

許多浏覽器供應商已經删除了對Java浏覽器插件的支援,或者将在不久的将來删除它。記住這一點,JDK 9不贊成使用Applet API。 java.applet包和

javax.swing.JApplet

類中的所有類型已被棄用。 appletviewer工具也已被棄用。

JDK 6通過

java.awt.Desktop

類添加了對平台特定桌面功能的有限支援,例如在使用者預設浏覽器中打開URI,在使用者預設郵件用戶端中打開mailto URI,以及使用注冊的應用打開,編輯和列印檔案。如果Java SE 9在目前平台上可用,許多系統和應用程式事件通知都會提供特定于平台的桌面支援,并為其添加了公共API支援。為了支援這麼多新的桌面功能,Java SE 9向java.desktop子產品添加了一個新的包java.awt.desktop。

java.awt.Desktop

類也增加了很多新的方法。在JDK 9中,Desktop API支援24個平台特定的桌面操作和通知,例如當附加的顯示進入或退出節電模式,系統進入睡眠模式或系統喚醒後的通知等。

為了解決反序列化帶來的安全風險,JDK 9引入了一個對象輸入過濾器的概念,可以用來驗證被反序列化的對象,如果沒有通過測試,則可以停止反序列化過程。對象輸入過濾器是新接口

java.io.ObjectInputFilter

的執行個體。可以指定可以在反序列化任何對象時使用的全系統全局過濾器。可以使用新的jdk.serialFilter系統屬性,使用JAVA_HOME\conf\security\java.security檔案中jdk.serialFilter的屬性,或使用

ObjectInputFilter.Config

setSerialFilter()

方法來指定全局過濾器。可以使用其

setObjectInputFilter()

方法在

ObjectInputStream

上設定本地過濾器,該方法将覆寫全局過濾器。

java.io.InputStream

類新增一個稱為

transferTo(OutputStream out)

的方法,可用于從輸入流讀取所有位元組,并将它們順序寫入指定的輸出流。該方法不關閉任一流。

java.nio.Buffer

類接收到兩個方法,

duplicate()

slice()

——可用于複制和拼接緩沖區。複制和分片緩沖區與原始緩沖區共享其内容。但是他們保持自己的位置,限定和标記,獨立于原始緩沖區。