JDK9新特性學(xué)習(xí)

一、前言

??自從Oracle在2017年9月推出JDK 9之后,JDK版本的更新也定為了每半年更新一次,截至目前,JDK 11即將發(fā)布正式版。不過目前大部分的生產(chǎn)環(huán)境的版本依舊是8的比較多,這里我們來了解下JDK 9及接下來JDK 10在語言上的新功能,這樣以后如果哪天要升級版本了,也好又有個印象。

二、JDK 9

1. 交互式REPL:JShell

??REPL,是一種快速運行語句的基于命令行的工具,其他語言基本都有這種交互式的開發(fā)環(huán)境,比如python等,而在Java的過去版本中,是沒有REPL的。如果你想執(zhí)行一個簡單的打印語句,那么我們首先要創(chuàng)建一個有main方法的類,然后通過javac命令進行編譯,再然后運行。而JShell的出現(xiàn)則是為了解決這個問題。

JShell簡單測試.png

這樣我們平時可以使用JShell來進行一些簡單的測試工作,并且JShell也可以通過tab鍵進行相應(yīng)的補全操作。

2. 集合框架的工廠方法:of

??在JDK 9之前,如果要創(chuàng)建一個不可變的集合,我們可以通過Collections的unmodifiable開頭的方法來進行創(chuàng)建,不過,JDK 9提供了一種更優(yōu)雅的創(chuàng)建方式:of方法。

在集合框架的基礎(chǔ)類型List,Set,Map中都提供了多個重載的of方法,來提供不可變的靜態(tài)工廠方法,of方法創(chuàng)建的集合要求參數(shù)不能為null。

List<Integer> list = List.of();
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("green", 1, "red", 2);

不過有關(guān)Map,還可以多說一點:

  1. Map的of方法參數(shù),是鍵值對依次挨著傳遞參數(shù)的;
  2. 感興趣的可以看下實現(xiàn)源碼,底層保存數(shù)據(jù)是通過一個final類型的Object數(shù)組,保存的方式是下標(biāo)i保存key,而i+1保存key對應(yīng)的value,當(dāng)然key和value都是不能為null的,底層通過Objects.requireNonNull方法進行非空判斷。
static final class MapN<K,V> extends AbstractImmutableMap<K,V> {
@Stable
final Object[] table; // pairs of key, value
@Stable
final int size; // number of pairs

MapN(Object... input) {
    // 奇偶校驗,也就是判斷key和value是否一一對應(yīng)
    if ((input.length & 1) != 0) { // implicit nullcheck of input
        throw new InternalError("length is odd");
    }
    size = input.length >> 1;

    int len = EXPAND_FACTOR * input.length;
    len = (len + 1) & ~1; // ensure table is even length
    table = new Object[len];

    for (int i = 0; i < input.length; i += 2) {
        @SuppressWarnings("unchecked")
            K k = Objects.requireNonNull((K)input[i]);
        @SuppressWarnings("unchecked")
            V v = Objects.requireNonNull((V)input[i+1]);
        int idx = probe(k);
        if (idx >= 0) {
            throw new IllegalArgumentException("duplicate key: " + k);
        } else {
            int dest = -(idx + 1);
            table[dest] = k;
            table[dest+1] = v;
        }
    }
}

具體有關(guān)不可變靜態(tài)工廠方法,可參考API文檔說明:https://docs.oracle.com/javase/9/docs/api/java/util/List.html#immutable

3. 接口中的私有方法

??JDK 8中接口引入了默認(rèn)方法和靜態(tài)方法,在JDK 9中則是引入了私有方法,私有方法是一種有實現(xiàn)的訪問修飾符是private的方法,默認(rèn)方法和靜態(tài)方法可以共享私有方法,因此在一定程度上這可以避免冗余代碼。

public interface MyInterface {

    void normalMethod();

    static void staticMethod() {
        initStatic();
    }

    default void defaultMethod() {
        init();
    }

    private static void initStatic() {
        System.out.println("private static method!");
    }

    private void init() {
        System.out.println("private method");
    }
}

不過需要注意的點:

  1. 私有方法遵循private修飾符屬性,無法被覆蓋;
  2. 靜態(tài)方法無法被覆蓋,并且靜態(tài)方法遵循static屬性,無法調(diào)用非static方法;
4. Stream API新增方法

在使用JDK 8的過程中,我們已經(jīng)了解到Stream API所帶來的便利,而JDK 9則給Stream接口又新增了4個方法:ofNullable,iteratetakeWhile,dropWhile,我們來看一下。

4.1 ofNullable方法
public static<T> Stream<T> ofNullable(T t) {
    return t == null ? Stream.empty()
                     : StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}

這個比較簡單,將單個元素轉(zhuǎn)換為Stream流,如果是null,則返回一個為空的流。

4.2 iterate方法
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

這是JDK 9添加的iterate的重載方法,原有的iterate方法是一種無限的操作,必須要有l(wèi)imit等類似這樣的操作來限制大小。而新添加的這個重載方法多了個條件參數(shù),來決定什么時候結(jié)束迭代循環(huán),比如說:

// 比如生成等差數(shù)列 0 3 6 9 12 15 18 21 24 27
Stream.iterate(0, n -> n + 3).limit(10).forEach(x -> System.out.print(x + " "));
System.out.println("\n----------------");
Stream.iterate(0, n -> n <= 27, n -> n + 3).forEach(x -> System.out.print(x + " "));

其實,可以說:

 Java SE 9's iterate() = Java SE 8's iterate() + Java SE 8's filter()
4.3 takeWhile方法

這個方法用于對Stream流進行一個條件過濾,返回過濾后的流。該方法是一個默認(rèn)方法。

default Stream<T> takeWhile(Predicate<? super T> predicate)

先看一個例子:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// output: 1 2 3 
stream.takeWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

System.out.println("\n------------------");

stream = Stream.of(1, 4, 2, 5, 3, 6, 7, 8, 9, 10);
// output: 1
stream.takeWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

需要注意的是過濾的方式是依次對Stream流的元素進行判斷,返回第一次判斷結(jié)果為false的情況下所過濾的流。

4.4 dropWhile方法

??dropWhile方法也是對Stream流的一個過濾,不過該方法恰好和takeWhile方法過濾到的流反過來,意思是說刪掉滿足過濾條件的元素,返回剩下的元素,還是拿上個例子來看:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// output: 4 5 6 7 8 9 10 
stream.dropWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

System.out.println("\n------------------");

stream = Stream.of(1, 4, 2, 5, 3, 6, 7, 8, 9, 10);
// output: 4 2 5 3 6 7 8 9 10 
stream.dropWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));

Stream API參考自:Java SE 9: Stream API Improvements

5. Optional類新增方法

Optional類是JDK 8提供的一種優(yōu)雅的處理null的解決方法,同樣,在JDK 9中,Optional類中也增加了幾個方法,我們來看一下。

5.1 stream方法
public Stream<T> stream() {
    if (!isPresent()) {
        return Stream.empty();
    } else {
        return Stream.of(value);
    }
}

該方法用于將一個Optional對象轉(zhuǎn)為Stream流,比如說:

Stream<Integer> stream = Optional.of(1).stream();

Stream<Optional<Integer>> os = ..;
Stream<T> s = os.flatMap(Optional::stream);
5.2 ifPresentOrElse方法
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
    if (value != null) {
        action.accept(value);
    } else {
        emptyAction.run();
    }
}

和原有的ifPresent方法很像,不過是多了一個else的判斷,也可也算是ifPresent方法和orElse方法的組合形式,其實直接看代碼就一目了然了。

5.3 or方法
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
    Objects.requireNonNull(supplier);
    if (isPresent()) {
        return this;
    } else {
        @SuppressWarnings("unchecked")
        Optional<T> r = (Optional<T>) supplier.get();
        return Objects.requireNonNull(r);
    }
}

返回一個Optional對象,如果不為空,返回對象本身,如果為空,返回我們通過Supplier指定的Optional 對象。

6. try-resource改進

我們先看代碼,再說改進的內(nèi)容:
JDK 9之前:

void testARM_Before_Java9() throws IOException {
    BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
    try (BufferedReader reader2 = reader1) {
        System.out.println(reader2.readLine());
    }
}

JDK9之后的代碼:

void testARM_Java9() throws IOException {
    BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
    try (reader1) {
        System.out.println(reader1.readLine());
    }
}

在JDK 9之前,try-resource塊需要滿足以下條件:

  • try塊中的資源需要實現(xiàn)java.lang.Autocloseable接口;
  • 資源的類型需要final或者間接是final類型的;
  • 如果資源在Try-With-Resources語句之外聲明,我們應(yīng)該重新引用局部變量(上面的reader1變量);
  • 新創(chuàng)建的局部變量在Try-With-Resources語句中是有效的;

JDK 9的話,如果我們已經(jīng)有一個在Try-With-Resource語句外面聲明為final或有效final的資源,那么我們就不需要再聲明一個局部變量。我們可以在Try-With-Resource語句中使用以前創(chuàng)建的變量。

7. @Deprecated注解增強

??在JDK 8和早期版本中,@Deprecated只是一個沒有任何方法的標(biāo)記接口,用于將某一個類,字段,方法,接口等對象標(biāo)記為廢棄狀態(tài)。
??而在JDK 9中,Oracle對該注解進行了增強,用于提供關(guān)于廢棄對象的更多信息,并且還提供了掃描jar文件的工具jdeprscan來分析應(yīng)用程序種使用廢棄API的情況。該注解新增了兩個參數(shù):sinceforRemoval,我們來簡單看下:

String since() default "";

boolean forRemoval() default false;
  1. since,表示該廢棄對象廢棄的版本;
  2. forRemoval,表示該廢棄元素在以后的版本是否會刪除,默認(rèn)是false;
8. Diamond (鉆石) 操作符 (<>)

鉆石操作符是JDK 7引入的,目的是為了自動識別泛型<> 尖括號內(nèi)的類型,以避免代碼的冗余,進而提高代碼的可讀性,舉個簡單的例子:

Map<String, String> map = new TreeMap<String, String>();

JDK 7引入鉆石操作符后:

Map<String, String> map2 = new TreeMap<>();

但是在JDK 9之前,鉆石操作符的使用有些限制,就是不能用于匿名內(nèi)部類上,而JDK 9則解決了這個問題:

List<String> list = new ArrayList<>(){
    @Override
    public boolean add(String s) {
        return super.add(s);
    }
};

List<String> list = new ArrayList<>(){};

以上代碼在JDK 8是編譯不通過的。

9. 響應(yīng)式流

JDK9中的Flow API對應(yīng)響應(yīng)式流規(guī)范,響應(yīng)式流規(guī)范是一種事實標(biāo)準(zhǔn)。JEP 266包含了一組最小接口集合,這組接口能捕獲核心的異步發(fā)布與訂閱。希望在未來第三方能夠?qū)崿F(xiàn)這些接口,并且能共享其方式。

java.util.concurrent.Flow包含以下4個接口:

  • Flow.Processor(處理器)
  • Flow.Publisher(發(fā)布者)
  • Flow.Subscriber(訂閱者)
  • Flow.Subscription(訂閱管理器)

這些接口都支持響應(yīng)式流發(fā)布-訂閱框架。Java 9也提供了實用類SubmissionPublisher。一個發(fā)布者產(chǎn)生一個或多個物品,這些物品由一個或多個消費者消耗。并且訂閱者由訂閱管理器管理。訂閱管理器連接發(fā)布者和訂閱者。

有關(guān)響應(yīng)式流,推薦閱讀:
Java 9 - 說說響應(yīng)式流
https://www.journaldev.com/20723/java-9-reactive-streams

10. CompletableFuture API 增強

??CompletableFuture 類我們前面說過,是一個Future的增強類,用于解決Future類所不太好解決的問題,比如說結(jié)果的獲取。而在JDK 9中,Oracle對該類也進行了增強用于支持一些延時和超時的操作,增加了一些常用方法和更好的子類化的操作方式。

11. Java模塊化系統(tǒng)-Jigsaw

??模塊化是JDK 9中最令人期待也是最重要的功能,模塊化和我們原先jar包的形式完全不同,我們都知道原先在JDK 9之前我們都是通過jar包來進行組織的,比如說,我們常用的rt.jar,tools.jar。而引入了模塊化系統(tǒng)之后,JDK被重新組織成94個模塊,我們常用的代碼比如java.util,java.lang等包都被組織到了java.base模塊下。

JDK 9包組織格式.png

JDK 8包組織格式.png

??通常情況下一個模塊包含著一個模塊描述符文件(module-info.java)用來指定模塊的名字、依賴(需要注意的是每個模塊必須顯式得聲明其依賴)、對外可使用的包(其余的包則對外不可見)、模塊提供的服務(wù)、模塊使用的服務(wù)以及允許哪些模塊可以對其進行反射等配置信息。這里需要注意下:

模塊描述符文件通常位于根目錄下;

感興趣的可以看下java.base模塊,幾乎包含了我們常用的所有相關(guān)包,如果我們要使用其他的模塊,在module-info.java進行相應(yīng)的引入:

module JDK9FirstDemo {
    requires java.sql;
}

模塊最終都會打包成 jar 包來分發(fā)和使用的,而打包則是通過JDK 9中的JLink來進行,那么模塊打包的 jar 包和普通的 jar 包有什么區(qū)別呢?模塊和普通的 jar 包幾乎是一樣的,只不過比普通的 jar 包在根目錄下多了一個模塊描述符文件—— module-info.class而已。

這里沒有詳細介紹模塊化有兩點原因:

  • 一是模塊化和我們之前的操作方式有很大區(qū)別,雖然JDK 提供了兼容方式,但目前還沒有大范圍的在生產(chǎn)環(huán)境上使用;
  • 這里先了解有這么個東西,等哪天模塊化普及了,再來仔細學(xué)習(xí)下。

如果有興趣的,推薦看下:Java 9 模塊化入門,這篇文章大概介紹了模塊化的歷史及模塊化中各個指令的使用。

三、總結(jié)

其實這里只列出了JDK 9部分可能常用的語言方法的特性,其實JDK 9還有許多特性:

  • 在JDK8中,可以單獨使用下劃線作為變量,但JDK 9變量不能被命名為_;
  • GC的改進;
  • Javadoc搜索;
  • 進程API的改進;
  • 統(tǒng)一的JVM日志處理;
  • HTTP 2客戶端API;
  • HTML 5風(fēng)格的javadoc文檔;
  • 多分辨率圖像API–JEP 251;
  • 廢棄Applet API;

如果以后有用到的,再來更新。

本文參考自:Java 9 Features with Examples
如果要查看 JDK 9全部更新,可參考:http://openjdk.java.net/projects/jdk9/
這里把JDK 10的一些更新也發(fā)出來,如果有需要,可以參考:https://www.journaldev.com/20395/java-10-features

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容