第18章_JDK8-17新特性

长名06發表於2024-08-28

此筆記中略的部分,在宋紅康老師的影片中和其附帶的筆記,都有詳細內容,這裡給出影片地址。

本章專題與脈絡

1.Java版本迭代概述

1.1 釋出特點(小步快跑,快速迭代)

發行版本 發行時間 備註
Java 1.0 1996.01.23 Sun 公司釋出了 Java 的第一個開發工具包
Java 5.0 2004.09.30 ①版本號從 1.4 直接更新至 5.0;②平臺更名為Java SE、Java EE、Java ME
Java 8.0 2014.03.18 此版本是繼 Java 5.0 以來變化最大的版本。是長期支援版本(Long-Term Support) 縮寫LTS
Java 9.0 2017.09.22 此版本開始,每半年更新一次
Java 10.0 2018.03.21
Java 11.0 2018.09.25 JDK 安裝包取消獨立 JRE 安裝包,是長期支援版本(LTS)
Java 12.0 2019.03.19
.... ...
Java17.0 2021.09 釋出 Java 17.0,版本號也稱為 21.9,是長期支援版本(LTS)
... ...
Java21.0 2023.09.19 釋出 Java21.0,也是長期支援版本(LTS)

這意味著 Java 的更新從傳統的以特性驅動的釋出週期,轉變為以時間驅動的釋出模式,並且承諾不會跳票。透過這樣的方式,開發團隊可以把一些關鍵特性儘早合併到 JDK 之中,以快速得到開發者反饋,在一定程度上避免出現像Java 9 兩次被迫延遲釋出的窘況。

針對企業客戶的需求,Oracle 將以三年為週期釋出長期支援版本(long term support)。

Oracle 的官方觀點認為:與 Java 7->8->9 相比,Java 9->10->11 的升級和8->8u20->8u40 更相似。

新模式下的 Java 版本釋出都會包含許多變更,包括語言變更JVM變更,這兩者都會對 IDE、位元組碼庫和框架產生重大影響。此外,不僅會新增其他API,還會有 API 被刪除(這在 Java 8 之前沒有發生過)。

目前看這種釋出策略是非常成功的,解開了 Java/JVM 演進的許多枷鎖,至關重要的是,OpenJDK 的權力中心,正在轉移到開發社群和開發者手中。在新的模式中,既可以利用 LTS 滿足企業長期可靠支援的需求,也可以滿足各種開發者對於新特性迭代的訴求。因為用 2-3 年的最小間隔粒度來試驗一個特性,基本是不現實的。

1.2 名詞解釋

名詞解釋:Oracle JDK 和 Open JDK

這兩個 JDK 最大不同就是許可證不一樣。但是對於個人使用者來講,沒區別。

Oracle JDK Open JDK
來源 Oracle 團隊維護 Oracle 和 Open Java 社群
授權協議 Java 17 及更高版本 Oracle Java SE 許可證
Java16 及更低版本甲骨文免費條款和條件(NFTC) 許可協議
GPL v2 許可證
關係 由 Open JDK 構建,增加了少許內容
是否收費 2021 年 9 月起 Java17 及更高版本所有使用者免費。16 及更低版本,個人使用者、開發使用者免費。 2017 年 9 月起,所有版本免費
對語法的支援 一致 一致

名詞解釋:JEP

JEP(JDK Enhancement Proposals):jdk 改進提案,每當需要有新的設想時候,JEP 可以提出非正式的規範(specification),被正式認可的 JEP 正式寫進 JDK 的發展路線圖並分配版本號。

名詞解釋:LTS

LTS(Long-term Support)即長期支援。Oracle 官網提供了對 Oracle JDK 個別版本的長期支援,即使發發行了新版本,比如目前最新的 JDK19,在結束日期前,LTS 版本都會被長期支援。(出了 bug,會被修復,非 LTS 則不會再有補丁釋出)所以,一定要選一個 LTS 版本,不然出了漏洞沒人修復了。

1.3 各版本支援時間路線圖


1.4 各版本介紹

下面關於各個版本的新特性,瞭解一下即可。

jdk 9

特性太多,檢視連結:

https://openjdk.java.net/projects/jdk9/

jdk 10

https://openjdk.java.net/projects/jdk/10/

286: Local-Variable Type Inference 區域性變數型別推斷

296: Consolidate the JDK Forest into a Single Repository JDK 庫的合併

304: Garbage-Collector Interface 統一的垃圾回收介面

307: Parallel Full GC for G1 為 G1 提供並行的 Full GC

310: Application Class-Data Sharing應用程式類資料(AppCDS)共享

312: Thread-Local Handshakes ThreadLocal 握手互動

313: Remove the Native-Header Generation Tool (javah) 移除 JDK 中附帶的 javah 工具

314: Additional Unicode Language-Tag Extensions 使用附加的 Unicode 語言標記擴充套件

316: Heap Allocation on Alternative Memory Devices 能將堆記憶體佔用分配給使用者指定的備用記憶體裝置

317: Experimental Java-Based JIT Compiler 使用 Graal 基於 Java 的編譯器

319: Root Certificates 根證書

322: Time-Based Release Versioning 基於時間定於的釋出版本

jdk 11

https://openjdk.java.net/projects/jdk/11/

181: Nest-Based Access Control 基於巢狀的訪問控制

309: Dynamic Class-File Constants 動態類檔案常量

315: Improve Aarch64 Intrinsics改進 Aarch64 Intrinsics 318: Epsilon: A No-Op Garbage Collector Epsilon — 一個 No-Op(無操作)的垃圾收集器

320: Remove the Java EE and CORBA Modules 刪除 Java EE 和 CORBA 模組

321: HTTP Client (Standard) HTTPClient API

323: Local-Variable Syntax for Lambda Parameters 用於 Lambda 引數的區域性變數語法

324: Key Agreement with Curve25519 and Curve448 Curve25519 和 Curve448 演算法的金鑰協議

327: Unicode 10

328: Flight Recorder 飛行記錄儀

329: ChaCha20 and Poly1305 Cryptographic Algorithms ChaCha20 和 Poly1305 加密演算法

330: Launch Single-File Source-Code Programs啟動單一檔案的原始碼程式

331: Low-Overhead Heap Profiling 低開銷的 Heap Profiling

332: Transport Layer Security (TLS) 1.3 支援 TLS 1.3

333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental) 可伸縮低延遲垃圾收集器

335: Deprecate the Nashorn JavaScript Engine 棄用 Nashorn JavaScript 引擎

336: Deprecate the Pack200 Tools and API 棄用 Pack200 工具和 API

jdk 12

https://openjdk.java.net/projects/jdk/12/

189:Shenandoah: A Low-Pause-Time Garbage Collector (Experimental) 低暫停時間的 GC

230: Microbenchmark Suite 微基準測試套件

325: Switch Expressions (Preview) switch 表示式

334: JVM Constants API JVM 常量 API 340: One AArch64 Port, Not Two 只保留一個 AArch64 實現

341: Default CDS Archives 預設類資料共享歸檔檔案

344: Abortable Mixed Collections for G1 可中止的 G1 Mixed GC

346: Promptly Return Unused Committed Memory from G1 G1 及時返回未使用的已分配記憶體

jdk 13

https://openjdk.java.net/projects/jdk/13/

350: Dynamic CDS Archives 動態 CDS 檔案

351: ZGC: Uncommit Unused Memory ZGC:取消使用未使用的記憶體

353: Reimplement the Legacy Socket API 重新實現舊版套接字 API

354: Switch Expressions (Preview) switch 表示式(預覽)

355: Text Blocks (Preview) 文字塊(預覽)

jdk 14

https://openjdk.java.net/projects/jdk/14/

305: Pattern Matching for instanceof (Preview) instanceof 的模式匹配

343: Packaging Tool (Incubator) 打包工具

345: NUMA-Aware Memory Allocation for G1 G1 的 NUMA-Aware 記憶體分配

349: JFR Event Streaming JFR 事件流

352: Non-Volatile Mapped Byte Buffers非易失性對映位元組緩衝區

358: Helpful NullPointerExceptions 實用的NullPointerExceptions

359: Records (Preview) (預覽)

361: Switch Expressions (Standard) Switch 表示式

362: Deprecate the Solaris and SPARC Ports棄用 Solaris 和 SPARC 埠

363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector 刪除併發標記掃描(CMS)垃圾回收器

364: ZGC on macOS

365: ZGC on Windows

366: Deprecate the ParallelScavenge + SerialOld GC Combination 棄用 ParallelScavenge + SerialOld GC 組合

367: Remove the Pack200 Tools and API 刪除Pack200 工具和 API

368: Text Blocks (Second Preview) 文字塊 (再次預覽)

370: Foreign-Memory Access API (Incubator) 外部儲存器訪問 API

jdk 15

https://openjdk.java.net/projects/jdk/15/

339: Edwards-Curve Digital Signature Algorithm (EdDSA) EdDSA 數字簽名演算法

360: Sealed Classes (Preview) 密封類(預覽)

371: Hidden Classes 隱藏類

372: Remove the Nashorn JavaScript Engine 移除Nashorn JavaScript 引擎

373: Reimplement the Legacy DatagramSocket API 重新實現 Legacy DatagramSocket API

374: Disable and Deprecate Biased Locking 禁用偏向鎖定

375: Pattern Matching for instanceof (Second Preview) instanceof 模式匹配(第二

次預覽)

377: ZGC: A Scalable Low-Latency Garbage Collector ZGC:一個可擴充套件的低延遲垃圾收集器

378: Text Blocks 文字塊

379: Shenandoah: A Low-Pause-Time Garbage Collector Shenandoah:低暫停時間垃圾收集器

381: Remove the Solaris and SPARC Ports 移除Solaris 和 SPARC 埠

383: Foreign-Memory Access API (Second Incubator) 外部儲存器訪問 API(第二次孵化版)

384: Records (Second Preview) Records(第二次預覽)

385: Deprecate RMI Activation for Removal 廢棄 RMI 啟用機制

jdk 16

https://openjdk.java.net/projects/jdk/16/

338: Vector API (Incubator) Vector API(孵化器)

347: Enable C++14 Language Features JDK C++的原始碼中允許使用 C++14 的語言特性

357: Migrate from Mercurial to Git OpenJDK 原始碼的版本控制從Mercurial (hg) 遷移到 git

369: Migrate to GitHub OpenJDK 原始碼的版本控制遷移到 github 上

376: ZGC: Concurrent Thread-Stack ProcessingZGC:併發執行緒處理

380: Unix-Domain Socket Channels Unix 域套接字通道

386: Alpine Linux Port 將 glibc 的 jdk 移植到使用 musl 的alpine linux 上

387: Elastic Metaspace 彈性元空間

388: Windows/AArch64 Port 移植 JDK 到 Windows/AArch64

389: Foreign Linker API (Incubator) 提供 jdk.incubator.foreign 來簡化 native code 的呼叫

390: Warnings for Value-Based Classes 提供基於值的類的警告

392: Packaging Tool jpackage 打包工具轉正

393: Foreign-Memory Access API (Third Incubator)

394: Pattern Matching for instanceofInstanceof 的模式匹配轉正

395: Records Records 轉正

396: Strongly Encapsulate JDK Internals by Default 預設情況下,封裝了 JDK 內部構件

397: Sealed Classes (Second Preview) 密封類

jdk 17

https://openjdk.java.net/projects/jdk/17/

306: Restore Always-Strict Floating-Point Semantics 恢復始終嚴格的浮點語義

356: Enhanced Pseudo-Random Number Generators 增強型偽隨機數生成器

382: New macOS Rendering Pipeline 新的 macOS 渲染管道

391: macOS/AArch64 Port macOS/AArch64 埠398: Deprecate the Applet API for Removal 棄用 Applet API 後續將進行刪除

403: Strongly Encapsulate JDK Internals 強封裝 JDK 的內部 API

406: Pattern Matching for switch (Preview) switch 模式匹配(預覽)

407: Remove RMI Activation 刪除 RMI 啟用機制

409: Sealed Classes 密封類轉正

410: Remove the Experimental AOT and JIT Compiler 刪除實驗性的AOT 和 JIT 編譯器

411: Deprecate the Security Manager for Removal 棄用即將刪除的安全管理器

412: Foreign Function & Memory API (Incubator) 外部函式和記憶體 API(孵化特性)

414: Vector API (Second Incubator) Vector API(第二次孵化特性)

415: Context-Specific Deserialization Filters 上下文特定的反序列化過濾器

1.5 JDK各版本下載連線

Oracle JDK

https://www.oracle.com/java/technologies/downloads/archive/

個人使用者免費,商業收費。

Open JDK

https://jdk.java.net/archive/

都免費。建議使用該版本,避免版權問題。

1.6 如何學習新特性

對於新特性,我們應該從哪幾個角度學習新特性呢?

  • 語法層面:

    • 比如 JDK5 中的自動拆箱、自動裝箱、enum、泛型

    • 比如 JDK8 中的 lambda 表示式、介面中的預設方法、靜態方法

    • 比如 JDK10 中區域性變數的型別推斷

    • 比如 JDK12 中的 switch

    • 比如 JDK13 中的文字塊

  • API 層面:

    • 比如 JDK8 中的 Stream、Optional、新的日期時間、HashMap 的底層結構變化

    • 比如 JDK9 中 String 的底層結構

    • 新的 / 過時的 API

  • 底層最佳化

    • 比如 JDK8 中永久代被元空間替代、新的 JS 執行引擎

    • 比如新的垃圾回收器、GC 引數、JVM 的最佳化

2.Java8新特性:Lambda表示式

其實Java 8從2014年到現在,從釋出的時間上,已經不算"新了"。只是為了表達是java8之後出現的,所以叫Java8新特性。一般語法上的新特性,是必須掌握的,不僅能夠提高開發效率,以及在各種框架中會使用到這些釋出時間久的"新特性"了。

2.1 關於Java8新特性簡介

Java 8 (又稱為 JDK 8 或 JDK1.8) 是 Java 語言開發的一個主要版本。 Java 8 是 Oracle 公司於 2014 年 3 月釋出,可以看成是自 Java 5 以來最具革命性的版本。Java 8 為 Java 語言、編譯器、類庫、開發工具與 JVM 帶來了大量新特性。

  • 速度更快

  • 程式碼更少(增加了新的語法:Lambda 表示式)

  • 強大的 Stream API

  • 便於並行

    • 並行流就是把一個內容分成多個資料塊,並用不同的執行緒分別處理每個資料塊的流。相比較序列的流,並行的流可以很大程度上提高程式的執行效率。

    • Java 8 中將並行進行了最佳化,我們可以很容易的對資料進行並行操作。Stream API 可以宣告性地透過 parallel() 與 sequential() 在並行流與順序流之間進行切換。

  • 最大化減少空指標異常:Optional

  • Nashorn 引擎,允許在 JVM 上執行 JS 應用

    • 發音“nass-horn”,是德國二戰時一個坦克的命名

    • JavaScript 執行在 jvm 已經不是新鮮事了,Rhino 早在 jdk6 的時候已經存在。現在替代 Rhino,官方的解釋是 Rhino 相比其他 JavaScript 引擎(比如Google 的 V8)實在太慢了,改造 Rhino 還不如重寫。所以 Nashorn 的效能也是其一個亮點。

    • Nashorn 專案在 JDK 9 中得到改進;在 JDK11 中 Deprecated,後續JDK15 版本中 remove。在 JDK11 中取以代之的是 GraalVM。(GraalVM 是一個執行時平臺,它支援 Java 和其他基於 Java 位元組碼的語言,但也支援其他語言,如 JavaScript,Ruby,Python 或 LLVM。效能是 Nashorn 的 2 倍以上。)

2.2 冗餘的匿名內部類

java.lang.Runnable 介面來定義任務內容,並使用 java.lang.Thread類來啟動該執行緒。程式碼如下:

public static void main(String[] args) {
     new Thread(new Runnable() {
         @Override
         public void run() {
         System.out.println("多執行緒任務執行!");
         }
     }).start(); // 啟動執行緒
 }

本著“一切皆物件”的思想,這種做法是無可厚非的:首先建立一個 Runnable 介面的匿名內部類物件來指定任務內容,再將其交給一個執行緒來啟動。

程式碼分析:

對於 Runnable 的匿名內部類用法,可以分析出幾點內容:

  • Thread 類需要 Runnable 介面作為引數,其中的抽象 run 方法是用來指定執行緒任務內容的核心;
  • 為了指定 run 的方法體,不得不需要 Runnable 介面的實現類;
  • 為了省去定義一個 RunnableImpl 實現類的麻煩,不得不使用匿名內部類;
  • 必須覆蓋重寫抽象 run 方法,所以方法名稱、方法引數、方法返回值不得不再寫一遍,且不能寫錯;
  • 而實際上,似乎只有方法體才是關鍵所在

2.3 Lambda及其使用舉例

Lambda 是一個匿名函式,我們可以把 Lambda 表示式理解為是一段可以傳遞的程式碼(將程式碼像資料一樣進行傳遞)。使用它可以寫出更簡潔、更靈活的程式碼。作為一種更緊湊的程式碼風格,使 Java 的語言表達能力得到了提升。

  • 從匿名內部類到Lambda的轉換舉例1
//未使用Lambda表示式
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("wind");
    }
};
r1.run();

System.out.println("*********");

/** Lambda表示式寫法*/
Runnable r2 = () -> {
    System.out.println("breeze");
};

r2.run();
  • 從匿名內部類到Lambda的轉換舉例2
//未使用Lambda表示式
Consumer<String> consumer = new Consumer<>(){
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
};
consumer.accept("微風和睦");
//使用Lambda表示式
Consumer<String> consumer2 = (s) -> {System.out.println(s);};
consumer2.accept("風和日麗");

2.4 語法

Lambda 表示式:在 Java 8 語言中引入的一種新的語法元素和運算子。這個運算子為 “->” , 該運算子被稱為 Lambda 運算子或箭頭運算子。它將 Lambda 分為兩個部分:

  • 左側:指定了 Lambda 表示式需要的引數列表
  • 右側:指定了 Lambda 體,是抽象方法的實現邏輯,也即 Lambda 表示式要執行的功能

語法格式一:無參,無返回值

 /**
 * 語法格式1:無參無返回值
 */
@Test
public void demo1(){
    //未使用Lambda表示式
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("wind");
        }
    };
    r1.run();

    System.out.println("*********");

    /** Lambda表示式寫法*/
    Runnable r2 = () -> {
        System.out.println("breeze");
    };

    r2.run();
}

語法格式二:Lambda 需要一個引數,但是沒有返回值。

/**
 * 語法格式2:需要一個引數,但是無返回值
 */
@Test
public void demo2(){
    //未使用Lambda表示式
    Consumer<String> consumer = new Consumer<>(){
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    consumer.accept("微風和睦");
    //使用Lambda表示式
    Consumer<String> consumer2 = (String s) -> {System.out.println(s);};
    consumer2.accept("風和日麗");
}

語法格式三:資料型別可以省略,因為可由編譯器推斷得出,稱為“型別推斷”

/**
 * 語法格式3:資料型別可以省略,因為可以由編譯器推斷得出,稱為型別推斷
 */
@Test
public void demo3(){
    //語法格式3使用前
    Consumer<String> consumer1 = (String s) -> {System.out.println(s);};
    consumer1.accept("風和日麗");

    //語法格式3使用後
    Consumer<String> consumer2 = (s) -> {System.out.println(s);};
    consumer2.accept("天旋地轉");

}

語法格式四:Lambda 若只需要一個引數時,引數的小括號可以省略

/**
 * 語法格式四:Lambda 若只需要一個引數時,引數的小括號可以省略
 */
@Test
public void demo4(){
    //語法格式4使用前
    Consumer<String> consumer1 = (s) -> {System.out.println(s);};
    consumer1.accept("天旋地轉");

    //語法格式4使用後
    Consumer<String> consumer2 = s -> {System.out.println(s);};
    consumer2.accept("英明神武");
}

語法格式五:Lambda 需要兩個或以上的引數,多條執行語句,並且可以有返回值

/**
 * 語法格式5:Lambda 需要兩個或以上的引數,多條執行語句,並且可以有返回值
 */
@Test
public void demo5(){
    //語法格式5使用前
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        }
    };
    System.out.println(com1.compare(12,21));
    //語法格式5使用後
    Comparator<Integer> com2 = (o1,o2) -> {
        System.out.println(o1);
        System.out.println(o2);
        return o1.compareTo(o2);
    };
    System.out.println(com1.compare(22,21));
}

語法格式六:當 Lambda 體只有一條語句時,return 與大括號若有,都可以省略

/**
 * 語法格式6:當 Lambda 體只有一條語句時,return 與大括號若有,都可以省略
 */
@Test
public void demo6(){
    //語法格式6使用前
    Comparator<Integer> com1 = (o1,o2) -> {
        return o1.compareTo(o2);
    };
    System.out.println(com1.compare(12,6));
    //語法格式六使用後
    Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
    System.out.println(com2.compare(12,21));
}

2.5 關於型別推斷

在語法格式三 Lambda 表示式中的引數型別都是由編譯器推斷得出的。Lambda 表示式中無需指定型別,程式依然可以編譯,這是因為 javac 根據程式的上下文,在編譯的後臺推斷出了引數的型別。Lambda 表示式的型別依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的“型別推斷”。

 /**
 * 關於型別推斷
 * Lambda 表示式中無需指定型別,程式依然可以編譯,這是因為 javac 根據程
 * 序的上下文,在後臺推斷出了引數的型別。Lambda 表示式的型別依賴於上下
 * 文環境,是由編譯器推斷出來的。這就是所謂的“型別推斷”。
 */
@Test
public void demo8(){
    //型別推斷 1
    ArrayList<String> list = new ArrayList<>();
    //型別推斷 2
    int[] arr = {1, 2, 3};
}

3.Java8新特性:函式式(Functional)介面

3.1 什麼是函式式介面

  • 只包含一個抽象方法(Single Abstract Method,簡稱 SAM)的介面,稱為函式式介面。當然該介面可以包含其他非抽象方法。
  • 你可以透過 Lambda 表示式來建立該介面的物件。(若 Lambda 表示式丟擲一個受檢異常(即:非執行時異常),那麼該異常需要在目標介面的抽象方法上進行宣告)。
  • 我們可以在一個介面上使用 @FunctionalInterface 註解,這樣做可以檢查它是否是一個函式式介面。同時 javadoc 也會包含一條宣告,說明這個介面是一個函式式介面。
  • java.util.function 包下定義了 Java 8 的豐富的函式式介面

3.2 如何理解函式時介面

  • Java 從誕生日起就是一直倡導“一切皆物件”,在 Java 裡面物件導向(OOP)程式設計是一切。但是隨著 python、scala 等語言的興起和新技術的挑戰,Java 不得不做出調整以便支援更加廣泛的技術要求,即 Java 不但可以支援 OOP 還可以支援 OOF(面向函式程式設計)

    • Java8 引入了 Lambda 表示式之後,Java 也開始支援函數語言程式設計。
    • Lambda 表示式不是 Java 最早使用的。目前 C++,C#,Python,Scala 等均支援 Lambda 表示式。
  • 物件導向的思想:

    • 做一件事情,找一個能解決這個事情的物件,呼叫物件的方法,完成事情。
  • 函數語言程式設計思想:

    • 只要能獲取到結果,誰去做的,怎麼做的都不重要,重視的是結果,不重視過程。
  • 在函數語言程式設計語言當中,函式被當做一等公民對待。在將函式作為一等公民的程式語言中,Lambda 表示式的型別是函式。但是在 Java8 中,有所不同。在 Java8 中,Lambda 表示式是物件,而不是函式,它們必須依附於一類特別的物件型別——函式式介面。

  • 簡單的說,在 Java8 中,Lambda 表示式就是一個函式式介面的例項。這就是 Lambda表示式和函式式介面的關係。也就是說,只要一個物件是函式式介面的例項,那麼該物件就可以用 Lambda 表示式來表示。

3.3 舉例

/**
 * Represents an operation that accepts a single input argument and returns no
 * result. Unlike most other functional interfaces, {@code Consumer} is expected
 * to operate via side-effects.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #accept(Object)}.
 *
 * @param <T> the type of the input to the operation
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

作為引數,傳遞lambda表示式

//未使用Lambda表示式
Consumer<String> consumer = new Consumer<>(){
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
};
consumer.accept("微風和睦");
//使用Lambda表示式
Consumer<String> consumer2 = s -> System.out.println(s);
consumer2.accept("風和日麗");

作為引數傳遞 Lambda 表示式:為了將 Lambda 表示式作為引數傳遞,接收 Lambda 表示式的引數型別必須是與該 Lambda 表示式相容的函式式介面的型別。

3.4 Java內建函式時介面

3.4.1 常用的

之前學過的介面,有些就是函式式介面,比如:

  • java.lang.Runnable
    • public void run()
  • java.lang.Iterable
    • public Iterator iterate()
  • java.lang.Comparable
    • public int compareTo(T t)
  • java.util.Comparator
    • public int compare(T t1, T t2)

3.4.2 四大核心函式式介面

函式式介面 稱謂 泛型 用途
Consumer 消費型介面 T 對型別為 T 的物件應用操作,包含方法:void accept(T t)
Supplier 供給型介面 返回型別為 T 的物件,包含方法:T get()
Function<T, R> 函式型介面 T, R 對型別為 T 的物件應用操作,並返回結果。結果是 R 型別的物件。包含方法:R apply(T t)
Predicate 判斷型介面 T 確定型別為 T 的物件是否滿足某約束,並返回 boolean 值。包含方法:boolean test(T t)

3.4.3 其他介面

型別 1:消費型介面

消費型介面的抽象方法特點:有形參,但是返回值型別是 void

介面名 抽象方法 描述
BiConsumer<T,U> void accept(T t, U u) 接收兩個物件用於完成功能
DoubleConsumer void accept(double value) 接收一個 double 值
IntConsumer void accept(int value) 接收一個 int 值
LongConsumer void accept(long value) 接收一個 long 值
ObjDoubleConsumer void accept(T t, double value) 接收一個物件和一個double 值
ObjIntConsumer void accept(T t, int value) 接收一個物件和一個 int值
ObjLongConsumer void accept(T t, long value) 接收一個物件和一個 long值

型別 2:供給型介面

這類介面的抽象方法特點:無參,但是有返回值

介面名 抽象方法 描述
BooleanSupplier boolean getAsBoolean() 返回一個 boolean 值
DoubleSupplier double getAsDouble() 返回一個 double 值
IntSupplier int getAsInt() 返回一個 int 值
LongSupplier long getAsLong() 返回一個 long 值

型別 3:函式型介面

這類介面的抽象方法特點:既有引數又有返回值

介面名 抽象方法 描述
UnaryOperator T apply(T t) 接收一個 T 型別物件,返回一個 T 型別物件結果
DoubleFunction R apply(double value) 接收一個 double值,返回一個 R 型別物件
IntFunction R apply(int value) 接收一個 int 值,返回一個 R 型別物件
LongFunction R apply(long value) 接收一個 long值,返回一個 R 型別物件
ToDoubleFunction double applyAsDouble(T value) 接收一個 T 型別物件,返回一個double
... ... ...

該函式型介面,太多不再舉例。具體見宋紅康老師影片課程第18章

型別 4:判斷型介面

這類介面的抽象方法特點:有參,但是返回值型別是 boolean 結果。

介面名 抽象方法 描述
BiPredicate<T,U> boolean test(T t, U u) 接收兩個物件
DoublePredicate boolean test(double value) 接收一個 double 值
IntPredicate boolean test(int value) 接收一個 int 值
LongPredicate boolean test(long value) 接收一個 long 值

4.Java8新特性:方法引用和構造器引用

表示式是可以簡化函式式介面的變數或形參賦值的語法。而方法引用和構造器引用是為了簡化 Lambda 表示式的。

4.1 方法引用

當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用!

方法引用可以看做是 Lambda 表示式深層次的表達。換句話說,方法引用就是Lambda 表示式,也就是函式式介面的一個例項,透過方法的名字來指向一個方法,可以認為是 Lambda 表示式的一個語法糖。

語法糖(Syntactic sugar),也譯為糖衣語法,是由英國電腦科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語,指計算機語言中新增的某種語法,這種語法對語言的功能並沒有影響,但是更方便程式設計師使用。通常來說使用語法糖能夠增加程式的可讀性,從而減少程式程式碼出錯的機會。

4.1.1 方法引用格式

  • 格式:使用方法引用運算子 :: 將類(或物件) 與 方法名分隔開來。
    • 兩個:中間不能有空格,而且必須英文狀態下半形輸入
  • 如下三種主要使用情況:
    • 情況 1:物件 :: 例項方法名
    • 情況 2: :: 靜態方法名
    • 情況 3: :: 例項方法名

4.1.2 方法引用前提

要求 1:

Lambda 體只有一句語句,並且是透過呼叫一個物件的/類現有的方法來完成的

例如:System.out 物件,呼叫 println()方法來完成 Lambda 體

Math 類,呼叫 random()靜態方法來完成 Lambda 體

要求 2:

針對情況 1:函式式介面中的抽象方法 a ,在被重寫時使用了某一個物件的方法b。如果方法 a 的形參列表、返回值型別與方法 b 的形參列表、返回值型別都相同,則我們可以使用方法 b 實現對方法 a 的重寫、替換。

針對情況 2:函式式介面中的抽象方法 a, 在被重寫時使用了某一個類的靜態方法 b。如果方法 a 的形參列表、返回值型別與方法 b 的形參列表、返回值型別都相同,則我們可以使用方法 b 實現對方法 a 的重寫、替換。

針對情況 3:函式式介面中的抽象方法 a ,在被重寫時使用了某一個物件的方法b。如果方法 a 的返回值型別與方法 b 的返回值型別相同,同時方法 a 的形參列表中有 n 個引數,方法 b 的形參列表有 n-1 個引數,且方法 a 的第 1 個引數作為方法 b 的呼叫者,且方法 a 的後 n-1 引數與方法 b 的 n-1 引數匹配(型別相同或滿足多型場景也可以)

例如:s->System.out.println(t)

() -> Math.random() 都是無參

4.1.3 舉例

//情況1:物件::例項方法
@Test
public void demo1() {

    //Consumer##void  accept(T t);
    //1.匿名內部類
    Consumer<String> consumer = new Consumer<>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    consumer.accept("微風和睦");
    //2.Lambda表示式
    Consumer<String> consumer2 = s -> System.out.println(s);
    consumer2.accept("風和日麗");

    //3.方法引用
    Consumer<String> consumer3 = System.out::println;
    consumer3.accept("學而不思則罔");
}

/**
 * Supplier##String get()
 */
@Test
public void demo2() {
    Person person = new Person("小明", 20);
    //1.匿名內部類
    Supplier<String> supplier = new Supplier<String>() {
        @Override
        public String get() {
            return person.getName();
        }
    };
    System.out.println(supplier.get());

    //2.Lambda表示式
    Supplier<String> supplier1 = () -> person.getName();
    System.out.println(supplier1.get());

    //3.方法引用
    Supplier<String> supplier2 = person::getName;
    System.out.println(supplier2.get());
}

/**
 * 情況2 類::靜態方法
 */
//Comparator##int compare(T o1, T o2);
@Test
public void demo3() {
    //1.匿名內部類
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    System.out.println(com1.compare(22, 21));

    //2.lambda表示式
    Comparator<Integer> con2 = (o1, o2) -> Integer.compare(o1, o2);
    System.out.println(con2.compare(2, 3));

    //3.方法引用
    Comparator<Integer> con3 = Integer::compare;
    con3.compare(3, 5);
}

//Function##R apply(T t);
//Math中的Long round(Double d);
@Test
public void demo4() {
    //1.匿名內部類
    Function<Double, Long> fun1 = new Function<Double, Long>() {
        @Override
        public Long apply(Double aDouble) {
            return Math.round(aDouble);
        }
    };

    //2.lambda表示式
    Function<Double, Long> fun2 = aDouble -> Math.round(aDouble);

    //3.方法引用
    Function<Double, Long> fun3 = Math::round;
}

/**
 * 情況3:類::例項方法
 */

//Comparator##int compare(T o1, T o2);
//String##int t1.compareTo(t2)
@Test
public void demo5(){

    //0.匿名內部類
    Comparator<String> comparator0 = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    };

    //1.Lambda表示式
    Comparator<String> comparator = (s1,s2) -> s1.compareTo(s2);
    System.out.println(comparator.compare("aab", "abb"));

    Comparator<String> comparator1 = String::compareTo;
    System.out.println(comparator1.compare("abb","aab"));
}

//BiPredicate##boolean test(T t, U u);
@Test
public void demo6(){

    //0.匿名內部類
    BiPredicate<String,String> biPredicate0 = new BiPredicate<String, String>() {
        @Override
        public boolean test(String s1, String s2) {
            return s1.equals(s2);
        }
    };

    //1.lambda表示式
    BiPredicate<String,String> biPredicate1 = (s1,s2) -> s1.equals(s2);
    //2.方法引用
    BiPredicate<String,String> biPredicate2 = String::equals;
}

@Test
public void demo7(){
    Person person = new Person("風", 23);

    //1.匿名內部類
    Function<Person,String> fun1 = new Function<Person, String>() {
        @Override
        public String apply(Person person) {
            return person.getName();
        }
    };
    System.out.println(fun1.apply(person));

    //2.lambda表示式
    Function<Person,String> fun2 = (p1) -> p1.getName();

    System.out.println(fun2.apply(person));
    //3.方法引用
    Function<Person,String> fun3 = Person::getName;
    System.out.println(fun3.apply(person));
}
//上面demo中使用的Person類
public class Person {

    private String name;

    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.2 構造器引用

當 Lambda 表示式是建立一個物件,並且滿足 Lambda 表示式形參,正好是給建立這個物件的構造器的實參列表,就可以使用構造器引用。

格式:類名::new

舉例

 //構造器引用
//Supplier<T>##get()
@Test
public void demo1() {

    //0.匿名內部類
    Supplier<Person> sup1 = new Supplier<Person>() {
        @Override
        public Person get() {
            return new Person();
        }
    };
    System.out.println(sup1.get());

    //1.方法引用
    Supplier<Person> sup2 = Person::new;
    System.out.println(sup2.get());
}

// Function<T,R>##R apply(T t)
@Test
public void demo2() {
    //0.匿名內部類
    Function<Integer, Person> fun1 = new Function<Integer, Person>() {
        @Override
        public Person apply(Integer age) {
            return new Person(age);
        }
    };
    System.out.println(fun1.apply(20));

    //1.構造器引用
    Function<Integer, Person> fun2 = Person::new;//呼叫的是Person##Person(int age)
    System.out.println(fun2.apply(23));
}

//BiFunction<T, U, R>##R apply(T t, U u)
@Test
public void demo3() {
    //0.匿名內部類
    BiFunction<String, Integer, Person> func1 = new BiFunction<String, Integer, Person>() {
        @Override
        public Person apply(String s, Integer integer) {
            return new Person(s, integer);
        }
    };

    //1.構造器引用
    BiFunction<String, Integer, Person> func2 = Person::new;//呼叫Person##Person(String name, int age)
    System.out.println(func2.apply("breeze", 20));
}

4.3 陣列構造引用

當 Lambda 表示式是建立一個陣列物件,並且滿足 Lambda 表示式形參,正好是給建立這個陣列物件的長度,就可以陣列構造引用。

格式:陣列型別名::new

//陣列引用
//Function中的R apply(T t)
@Test
public void demo4(){

    //0.匿名內部類
    Function<Integer,Person[]> func1 = new Function<Integer, Person[]>() {
        @Override
        public Person[] apply(Integer length) {
            return new Person[length];
        }
    };

    //2.方法引用
    Function<Integer,Person[]> func2 = Person[]::new;;
    System.out.println(func2.apply(10).length);
}

5. Java8 新特性:強大的 Stream API

5.1 說明

Java8 中有兩大最為重要的改變。第一個是 Lambda 表示式;另外一個則是 Stream API。

Stream API ( java.util.stream) 把真正的函數語言程式設計風格引入到 Java 中。這是目前為止對 Java 類庫最好的補充,因為 Stream API 可以極大提供 Java 程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的程式碼。

Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作。 使用 Stream API 對集合資料進行操作,就類似於使用 SQL 執行的資料庫查詢。也可以使用 Stream API 來並行執行操作。簡言之,Stream API 提供了一種高效且易於使用的處理資料的方式。

5.2 為什麼要使用Stream API

實際開發中,專案中多數資料來源都來自於 MySQL、Oracle 等。但現在資料來源可以更多了,有 MongDB,Radis 等,而這些 NoSQL 的資料就需要 Java 層面去處理。

5.3 什麼是Stream

Stream 是資料渠道,用於運算元據源(集合、陣列等)所生成的元素序列。Stream 和 Collection 集合的區別:Collection 是一種靜態的記憶體資料結構,講的是資料,而 Stream 是有關計算的,講的是計算。前者是主要面向記憶體,儲存在記憶體中,後者主要是面向 CPU,透過 CPU 實現計算。

注意:

①Stream 自己不會儲存元素。

②Stream 不會改變源物件。相反,他們會返回一個持有結果的新 Stream。

③Stream 操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。即一旦執行終止操作,就執行中間操作鏈,併產生結果。

④ Stream 一旦執行了終止操作,就不能再呼叫其它中間操作或終止操作了。

5.4 Stream的散佈操作

1- 建立 Stream 一個資料來源(如:集合、陣列),獲取一個流。

2- 中間操作 每次處理都會返回一個持有結果的新 Stream,即中間操作的方法返回值仍然是 Stream 型別的物件。因此中間操作可以是個操作鏈,可對資料來源的資料進行 n 次處理,但是在終結操作前,並不會真正執行。

3- 終止操作(終端操作) 終止操作的方法返回值型別就不再是 Stream 了,因此一旦執行終止操作,就結束整個 Stream 操作了。一旦執行終止操作,就執行中間操作鏈,最終產生結果並結束 Stream。

5.4.1 建立 Stream 例項

方式一:透過集合

Java8 中的 Collection 介面被擴充套件,提供了兩個獲取流的方法:

  • default Stream stream() : 返回一個順序流
  • default Stream parallelStream() : 返回一個並行流
//方式1 透過集合
@Test
public void demo1(){
    List<Person> list = PersonData.getPersons();
    //順序流
    //default Stream<E> stream()
    Stream<Person> stream = list.stream();
    //並行流
    //default Stream<E> parallelStream(
    Stream<Person> personStream = list.parallelStream();
}

方式二:透過陣列

Java8 中的 Arrays 的靜態方法 stream() 可以獲取陣列流:

  • static Stream stream(T[] array): 返回一個流
  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)
//方式2 透過陣列
@Test
public void demo2(){
    int[] arr = {1,2,3,4};
    IntStream stream = Arrays.stream(arr);

}

方式三:透過 Stream 的 of()

可以呼叫 Stream 類靜態方法 of(), 透過顯示值建立一個流。它可以接收任意數量的引數。

  • public static Stream of(T... values) : 返回一個流
//方式3 透過 Stream#of
@Test
public void demo3(){
    Stream<String> stream = Stream.of("AA", "BB", "CC", "DD");
}

5.4.2 一系列中間操作

多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱為惰性求值

1-篩選與切片

方 法 描 述
filter(Predicate p) 接收 Lambda , 從流中排除某些元素
distinct() 篩選,透過流所生成元素的 hashCode() 和 equals() 去除重複元素
limit(long maxSize) 截斷流,使其元素不超過給定數量
skip(long n) 跳過元素,返回一個扔掉了前 n 個元素的流。
若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補

2-對映

此處的對映,是類似y和x的一元方程的對映關係

方 法 描 述
map(Function f) 接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素。
mapToDouble(ToDoubleFunction f) 接收一個函式作為引數,該函式會被應用到每個元素上,產生一個新的DoubleStream。
mapToInt(ToIntFunction f) 接收一個函式作為引數,該函式會被應用到每個元素上,產生一個新的IntStream。
mapToLong(ToLongFunction f) 接收一個函式作為引數,該函式會被應用到每個元素上,產生一個新的LongStream。
flatMap(Function f) 接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流。

3-排序

方法 描述
sorted() 產生一個新流,其中按自然順序排序。
sorted(Comparator com) 產生一個新流,其中按比較器順序排序

5.4.3 終止操作

  • 終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
  • 流進行了終止操作後,不能再次使用。
方 法 描 述
collect(Collector c) 將流轉換為其他形式。接收一個 Collector 介面的實現,
用於給 Stream 中元素做彙總的方法

Collector 介面中方法的實現決定了如何對流執行收集的操作(如收集到 List、Set、Map)。

另外, Collectors 實用類提供了很多靜態方法,可以方便地建立常見收集器例項,具體方法與例項如下表:

方 法 返回型別 作用
toList Collector<T, ?, List> 把流中元素收集到 List
List<Employee> emps= list.stream().collect(Collectors.toList());
方 法 返回型別 作用
toSet Collector<T, ?, Set> 把流中元素收集到 Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
方 法 返回型別 作用
toCollection Collector<T, ?, C> 把流中元素收集到建立的集合
Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
方 法 返回型別 作用
counting Collector<T, ?, Long> 計算流中元素的個數
long count = list.stream().collect(Collectors.counting());
方 法 返回型別 作用
summingInt Collector<T, ?, Integer> 對流中元素的整數屬性求和
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
方 法 返回型別 作用
averagingInt Collector<T, ?, Double> 計算流中元素 Integer 屬性的平均值
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
方 法 返回型別 作用
summarizingInt Collector<T, ?, IntSummaryStatistics> 收集流中 Integer 屬性的統計值。如:平均值
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
方 法 返回型別 作用
joining Collector<CharSequence, ?, String> 連線流中每個字串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());

還有其他終止操作略。

5.5 Java9 新增 API

新增 1:Stream 例項化方法

ofNullable()的使用:

中 Stream 不能完全為 null,否則會報空指標異常。而 Java 9 中的ofNullable 方法允許我們建立一個單元素 Stream,可以包含一個非空元素,也可以建立一個空 Stream。

6. 新語法結構

新的語法結構,為我們勾勒出了 Java 語法進化的一個趨勢,將開發者從複雜、繁瑣的低層次抽象中逐漸解放出來,以更高層次、更優雅的抽象,既降低程式碼量,又避免意外程式設計錯誤的出現,進而提高程式碼質量和開發效率。

6.1 Java 的 REPL 工具: jShell 命令

JDK9 的新特性

Java 終於擁有了像 Python 和 Scala 之類語言的 REPL 工具(互動式程式設計環境,read - evaluate - print - loop):jShell。以互動式的方式對語句和表示式進行求值。即寫即得、快速執行。

利用 jShell 在沒有建立類的情況下,在命令列裡直接宣告變數,計算表示式,執行語句。無需跟人解釋”public static void main(String[] args)”這句"廢話"。

實際中,使用較少,略。

6.2 異常處理之 try-catch 資源關閉

在 JDK7 之前,我們這樣處理資源的關閉:

@Test
public void test01() {
     FileWriter fw = null;
     BufferedWriter bw = null;
     try {
         fw = new FileWriter("d:/1.txt");
         bw = new BufferedWriter(fw);
         bw.write("hello");
     } catch (IOException e) {
     	e.printStackTrace();
     } finally {
         try {
             if (bw != null) {
                bw.close();
             }
         } catch (IOException e) {
         	e.printStackTrace();
         }
         try {
            if (fw != null) {
             	fw.close();
         	}
         } catch (IOException e) {
         	e.printStackTrace();
         }
     }
}

JDK7 的新特性

在 try 的後面可以增加一個(),在括號中可以宣告流物件並初始化。try 中的程式碼執行完畢,會自動把流物件釋放,就不用寫 finally 了。

格式:

try(資源物件的宣告和初始化){
 	業務邏輯程式碼,可能會產生異常
}catch(異常型別1 e){
 	處理異常程式碼
}catch(異常型別2 e){ 
    處理異常程式碼
}

說明:

1、在 try()中宣告的資源,無論是否發生異常,無論是否處理異常,都會自動關閉資源物件,不用手動關閉了。

2、這些資源實現類必須實現 AutoCloseable 或 Closeable 介面,實現其中的close()方法。Closeable 是 AutoCloseable 的子介面。Java7 幾乎把所有的“資源類”(包括檔案 IO 的各種類、JDBC 程式設計的 Connection、Statement 等介面…)都進行了改寫,改寫後資源類都實現了 AutoCloseable 或 Closeable 介面,並實現了 close()方法。

3、寫到 try()中的資源類的變數預設是 final 宣告的,不能修改。

@Test
public void test02() {
     try (
         FileWriter fw = new FileWriter("d:/1.txt");
         BufferedWriter bw = new BufferedWriter(fw);
     ) {
     	bw.write("hello");
     } catch (IOException e) {
     	e.printStackTrace();
     }
}

JDK9 的新特性

try 的前面可以定義流物件,try 後面的()中可以直接引用流物件的名稱。在 try程式碼執行完畢後,流物件也可以釋放掉,也不用寫 finally 了。

格式:

A a = new A();
B b = new B();
try(a;b){
	可能產生的異常程式碼
}catch(異常類名 變數名){
	異常處理的邏輯
}
@Test
public void test04() {
     InputStreamReader reader = new InputStreamReader(System.in);
     OutputStreamWriter writer = new OutputStreamWriter(System.out);
     try (reader; writer) {
         //reader 是 final 的,不可再被賦值
         // reader = null;
     } catch (IOException e) {
     	e.printStackTrace();
     }
}

6.3 區域性變數型別推斷(個人不推薦使用)

JDK 10 的新特性

區域性變數的顯示型別宣告,常常被認為是不必須的,給一個好聽的名字反而可以很清楚的表達出下面應該怎樣繼續。本新特性允許開發人員省略通常不必要的區域性變數型別宣告,以增強 Java 語言的體驗性、可讀性。只是一個語法糖,編譯過程中會把使用的var替換成各自的型別。

//1.區域性變數的例項化
var list = new ArrayList<String>();
var set = new LinkedHashSet<Integer>();
//2.增強 for 迴圈中的索引
for (var v : list) {
 System.out.println(v);
}
//3.傳統 for 迴圈中
for (var i = 0; i < 100; i++) {
 System.out.println(i);
}
//4. 返回值型別含複雜泛型結構
var iterator = set.iterator();
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
  • 不適用場景
    • 宣告一個成員變數
    • 宣告一個陣列變數,併為陣列靜態初始化(省略 new 的情況下)
    • 方法的返回值型別
    • 方法的引數型別
    • 沒有初始化的方法內的區域性變數宣告
    • 作為 catch 塊中異常型別
    • Lambda 表示式中函式式介面的型別
    • 方法引用中函式式介面的型別

注意:

  • var 不是一個關鍵字,而是一個型別名,將它作為變數的型別。不能使用 var 作為類名。
  • 這不是 JavaScript。var 並不會改變 Java 是一門靜態型別語言的事實。編譯器負責推斷出型別,並把結果寫入位元組碼檔案,就好像是開發人員自己敲入型別一樣。

6.4 instanceof 的模式匹配

JDK14 中預覽特性:

instanceof 模式匹配透過提供更為簡便的語法,來提高生產力。有了該功能,可以減少 Java 程式中顯式強制轉換的數量,實現更精確、簡潔的型別安全的程式碼。

Java 14 之前舊寫法:

if(obj instanceof String){
 String str = (String)obj; //需要強轉
 .. str.contains(..)..
}else{
 ...
}

Java 14 之前新寫法:

if(obj instanceof String str){
 .. str.contains(..)..
}else{
 ...
}

舉例:略

JDK15 中第二次預覽:

沒有任何更改。

JDK16 中轉正特性:

在 Java16 中轉正。

6.5 switch 表示式

  • 傳統 switch 宣告語句的弊端:
    • 匹配是自上而下的,如果忘記寫 break,後面的 case 語句不論匹配與否都會執行; --->case 穿透
    • 所有的 case 語句共用一個塊範圍,在不同的 case 語句定義的變數名不能重複;
    • 不能在一個 case 裡寫多個執行結果一致的條件;
    • 整個 switch 不能作為表示式返回值。
//常見錯誤實現
switch(month){
     case 3|4|5://3|4|5 用了位運算子,11 | 100 | 101 結果是 111 是 7
     System.out.println("春季");
     break;
     case 6|7|8://6|7|8 用了位運算子,110 | 111 | 1000 結果是 1111 是 15
     System.out.println("夏季");
     break;
     case 9|10|11://9|10|11 用了位運算子,1001 | 1010 | 1011 結果是 1011
    是 11
     System.out.println("秋季");
     break;
     case 12|1|2://12|1|2 用了位運算子,1100 | 1 | 10 結果是 1111,是 15
     System.out.println("冬季");
     break;
     default:
     System.out.println("輸入有誤");
}

JDK12 中預覽特性:

  • Java 12 將會對 switch 宣告語句進行擴充套件,使用 case L ->來替代以前的 break;省去了 break 語句,避免了因少寫 break 而出錯。
  • 同時將多個 case 合併到一行,顯得簡潔、清晰,也更加優雅的表達邏輯分支。
  • 為了保持相容性,case 條件語句中依然可以使用字元:,但是同一個 switch 結構裡不能混用-> 和:,否則編譯錯誤。

Java 12 之前

public class SwitchTest {
	public static void main(String[] args) {
		int numberOfLetters;
		Fruit fruit = Fruit.APPLE;
		switch (fruit) {
             case PEAR:
                 numberOfLetters = 4;
                 break;
             case APPLE:
             case GRAPE:
             case MANGO:
                 numberOfLetters = 5;
                 break;
             case ORANGE:
             case PAPAYA:
                numberOfLetters = 6;
                break;
             default:
                throw new IllegalStateException("No Such Fruit:" + fruit);
        }
		System.out.println(numberOfLetters);
	}
}
enum Fruit {
 PEAR, APPLE, GRAPE, MANGO, ORANGE, PAPAYA;
}

switch 語句如果漏寫了一個 break,那麼邏輯往往就跑偏了,這種方式既繁瑣,又容易出錯。

Java12中

public class SwitchTest1 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        switch(fruit){
            case PEAR -> System.out.println(4);
            case APPLE,MANGO,GRAPE -> System.out.println(5);
            case ORANGE,PAPAYA -> System.out.println(6);
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
	}
}

更進一步

public class SwitchTest2 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        int numberOfLetters = switch(fruit){
            case PEAR -> 4;
            case APPLE,MANGO,GRAPE -> 5;
            case ORANGE,PAPAYA -> 6;
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
    	System.out.println(numberOfLetters);
    }
}

JDK13 中二次預覽特性:

JDK13 中引入了 yield 語句,用於返回值。這意味著,switch 表示式(返回值)應該使用 yield,switch 語句(不返回值)應該使用 break。

yield 和 return 的區別在於:return 會直接跳出當前迴圈或者方法,而 yield 只會跳出當前 switch 塊。

在以前:

@Test
public void testSwitch1(){
    String x = "3";
    int i;
    switch (x) {
        case "1":
        	i=1;
        	break;
        case "2":
            i=2;
            break;
        default:
            i = x.length();
            break;
    }
    System.out.println(i);
}

在 JDK13 中:

@Test
public void testSwitch2(){
    String x = "3";
    int i = switch (x) {
        case "1" -> 1;
        case "2" -> 2;
        default -> {yield 3;}
    };
    System.out.println(i);
}
//或者
@Test
public void testSwitch3() {
    String x = "3";
    int i = switch (x) {
        case "1":
        	yield 1;
        case "2":
        	yield 2;
        default:
        	yield 3;
    };
    System.out.println(i);
}

JDK14 中轉正特性:

這是 JDK 12 和 JDK 13 中的預覽特性,現在是正式特性了。

JDK17 的預覽特性:switch 的模式匹配

舊寫法:

static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
    	formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
    	formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
    	formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
    	formatted = String.format("String %s", s);
    }
    return formatted;
}

模式匹配新寫法:

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l -> String.format("long %d", l);
        case Double d -> String.format("double %f", d);
        case String s -> String.format("String %s", s);
        default -> o.toString();
    };
}

直接在 switch 上支援 Object 型別,這就等於同時支援多種型別,使用模式匹配得到具體型別,大大簡化了語法量,這個功能很實用。

6.6 文字塊

現實問題:

在 Java 中,通常需要使用 String 型別表達 HTML,XML,SQL 或 JSON 等格式的字串,在進行字串賦值時需要進行轉義和連線操作,然後才能編譯該程式碼,這種表達方式難以閱讀並且難以維護。

JDK13 的新特性

使用"""作為文字塊的開始符和結束符,在其中就可以放置多行的字串,不需要進行任何轉義。因此,文字塊將提高 Java 程式的可讀性和可寫性。

基本使用:

"""
line1
line2
line3
"""

相當於:
"line1\nline2\nline3\n"

或者一個連線的字串:

"line1\n" +
"line2\n" +
"line3\n"

如果字串末尾不需要行終止符,則結束分隔符可以放在最後一行內容上。
例如:
"""
line1
line2
line3"""

相當於
"line1\nline2\nline3"

文字塊可以表示空字串,但不建議這樣做,因為它需要兩行原始碼:
String empty = """
""";

舉例 1:普通文字

//原有寫法:
String text1 = "The Sound of silence\n" +

 "Hello darkness, my old friend\n" +

 "I've come to talk with you again\n" +

 "Because a vision softly creeping\n" +

 "Left its seeds while I was sleeping\n" +

 "And the vision that was planted in my brain\n" +

 "Still remains\n" +

 "Within the sound of silence";

System.out.println(text1)
    
//新特性
String text2 = """
         The Sound of silence
         Hello darkness, my old friend
         I've come to talk with you again
         Because a vision softly creeping
         Left its seeds while I was sleeping
         And the vision that was planted in my brain
         Still remains
         Within the sound of silence
 """;
System.out.println(text2);

舉例 2:HTML 語句

<html>
 <body>
 <p>Hello, 尚矽谷</p>
 </body>
</html>
將其複製到 Java 的字串中,會展示成以下內容:
"<html>\n" +
" <body>\n" +
" <p>Hello, 尚矽谷</p>\n" +
" </body>\n" +
"</html>\n";
即被自動進行了轉義,這樣的字串看起來不是很直觀,在 JDK 13 中:
"""
<html>
 <body>
 <p>Hello, world</p>
 </body>
</html>
""";

舉例 3:SQL 語句

select employee_id,last_name,salary,department_id
from employees
where department_id in (40,50,60)
order by department_id asc
//原有方式:
String sql = "SELECT id,NAME,email\n" +
             "FROM customers\n" +
             "WHERE id > 4\n" +
             "ORDER BY email DESC";
//使用新特性:
String sql1 = """
             SELECT id,NAME,email
             FROM customers
             WHERE id > 4
             ORDER BY email DESC
             """;

舉例 4:JSON 字串

//原有方式:
String myJson = "{\n" +
                 " \"name\":\"Song Hongkang\",\n" +
                 " \"address\":\"www.atguigu.com\",\n" +
                 " \"email\":\"shkstart@126.com\"\n" +
                 "}";
System.out.println(myJson);
//使用新特性:
String myJson1 = """
                 {
                 "name":"Song Hongkang",
                 "address":"www.atguigu.com",
                 "email":"shkstart@126.com"
                 }""";
System.out.println(myJson1);

JDK14 中二次預覽特性

JDK14 的版本主要增加了兩個 escape sequences,分別是\ \s escape sequence

public class Feature05 {
 //jdk14 新特性
    @Test
    public void test5(){
        String sql1 = """
                    SELECT id,NAME,email
                    FROM customers
                    WHERE id > 4
                    ORDER BY email DESC
                    """;
        System.out.println(sql1);
        // \:取消換行操作
        // \s:表示一個空格
        String sql2 = """
                    SELECT id,NAME,email \
                    FROM customers\s\
                    WHERE id > 4 \
                    ORDER BY email DESC
                    """;
        System.out.println(sql2);
    }
}

JDK15 中功能轉正

6.7 Record

背景

早在 2019 年 2 月份,Java 語言架構師 Brian Goetz,曾寫文抱怨“Java 太囉嗦”或有太多的“繁文縟節”。他提到:開發人員想要建立純資料載體類(plain data carriers)通常都必須編寫大量低價值、重複的、容易出錯的程式碼。如:建構函式、getter/setter、equals()、hashCode()以及 toString()等。

以至於很多人選擇使用 IDE 的功能來自動生成這些程式碼。還有一些開發會選擇使用一些第三方類庫,如 Lombok 等來生成這些方法。

JDK14 中預覽特性:神說要用 record,於是就有了。實現一個簡單的資料載體類,為了避免編寫:建構函式,訪問器,equals(),hashCode () ,toString ()等,Java 14 推出 record。

record 是一種全新的型別,它本質上是一個 final 類,同時所有的屬性都是final 修飾,它會自動編譯出 public gethashcodeequalstoString、構造器等結構,減少了程式碼編寫量。

具體來說:當你用 record 宣告一個類時,該類將自動擁有以下功能:

  • 獲取成員變數的簡單方法,比如例題中的 name() 和 partner() 。注意區別於我們平常 getter()的寫法。
  • 一個 equals 方法的實現,執行比較時會比較該類的所有成員屬性。
  • 重寫 hashCode() 方法。
  • 一個可以列印該類所有成員屬性的 toString() 方法。
  • 只有一個構造方法。

此外:

  • 還可以在 record 宣告的類中定義靜態欄位、靜態方法、構造器或例項方法。
  • 不能在 record 宣告的類中定義例項欄位;類不能宣告為 abstract;不能宣告顯式的父類等。
public record Dog(String name, Integer age) {
}
public class Java14Record {
    public static void main(String[] args) {
        Dog dog1 = new Dog("牧羊犬", 1);
        Dog dog2 = new Dog("田園犬", 2);
        Dog dog3 = new Dog("哈士奇", 3);
        System.out.println(dog1);
        System.out.println(dog2);
        System.out.println(dog3);
    }
}
public class Feature07 {
    @Test
    public void test1(){
        //測試構造器
        Person p1 = new Person("羅密歐",new Person("zhuliye",null));
        //測試 toString()
        System.out.println(p1);
        //測試 equals():
        Person p2 = new Person("羅密歐",new Person("zhuliye",null));
        System.out.println(p1.equals(p2));
        //測試 hashCode()和 equals()
        HashSet<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);
        for (Person person : set) {
        System.out.println(person);
        }
        //測試 name()和 partner():類似於 getName()和 getPartner()
        System.out.println(p1.name());
 		System.out.println(p1.partner());
    }
    @Test
    public void test2(){
        Person p1 = new Person("zhuyingtai");
        System.out.println(p1.getNameInUpperCase());
        Person.nation = "CHN";
        System.out.println(Person.showNation());
    }
}

public record Person(String name,Person partner) {
    //還可以宣告靜態的屬性、靜態的方法、構造器、例項方法
    public static String nation;
    public static String showNation(){
    	return nation;
    }
    public Person(String name){
    	this(name,null);
    }
    public String getNameInUpperCase(){
    	return name.toUpperCase();
	}
 //不可以宣告非靜態的屬性
// private int id;//報錯
}
//不可以將 record 定義的類宣告為 abstract 的
//abstract record Order(){
//
//}
//不可以給 record 定義的類宣告顯式的父類(非 Record 類)
//record Order() extends Thread{
//
//}

JDK15 中第二次預覽特性

JDK16 中轉正特性

最終到 JDK16 中轉正。

記錄不適合哪些場景

record 的設計目標是提供一種將資料建模為資料的好方法。它也不是JavaBeans 的直接替代品,因為 record 的方法不符合 JavaBeans 的 get 標準。另外 JavaBeans 通常是可變的,而記錄是不可變的。儘管它們的用途有點像,但記錄並不會以某種方式取代 JavaBean。

6.8 密封類

背景:

在 Java 中如果想讓一個類不能被繼承和修改,這時我們應該使用 final 關鍵字對類進行修飾。不過這種要麼可以繼承,要麼不能繼承的機制不夠靈活,有些時候我們可能想讓某個類可以被某些型別繼承,但是又不能隨意繼承,是做不到的。Java 15 嘗試解決這個問題,引入了 sealed 類,被 sealed 修飾的類可以指定子類。這樣這個類就只能被指定的類繼承。

JDK15 的預覽特性:

透過密封的類和介面來限制超類的使用,密封的類和介面限制其它可能繼承或實現它們的其它類或介面。

具體使用:

  • 使用修飾符 sealed,可以將一個類宣告為密封類。密封的類使用保留關鍵字permits 列出可以直接擴充套件(即 extends)它的類。
  • sealed 修飾的類的機制具有傳遞性,它的子類必須使用指定的關鍵字進行修飾,且只能是 finalsealednon-sealed 三者之一。
public abstract sealed class Shape permits Circle, Rectangle, Square {...}

public final class Circle extends Shape {...} //final 表示 Circle 不能再被繼承了

public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle {...}

public final class TransparentRectangle extends Rectangle {...}

public final class FilledRectangle extends Rectangle {...}

public non-sealed class Square extends Shape {...} //non-sealed 表示可以允許任何類繼承

JDK16 二次預覽特性

JDK17 中轉正特性

7. API 的變化

7.1 Optional 類

JDK8 的新特性

到目前為止,臭名昭著的空指標異常是導致 Java 應用程式失敗的最常見原因。以前,為了解決空指標異常,Google 在著名的 Guava 專案引入了 Optional類,透過檢查空值的方式避免空指標異常。受到 Google 的啟發,Optional 類已經成為 Java 8 類庫的一部分。

Optional 類(java.util.Optional) 是一個容器類,它可以儲存型別 T 的值,代表這個值存在。或者僅僅儲存 null,表示這個值不存在。如果值存在,則isPresent()方法會返回 true,呼叫 get()方法會返回該物件。

Optional 提供很多有用的方法,這樣我們就不用顯式進行空值檢測。

建立 Optional 類物件的方法:

  • static Optional empty() :用來建立一個空的 Optional 例項
    • static Optional of(T value) :用來建立一個 Optional 例項,value 必須非空
    • static Optional ofNullable(T value) :用來建立一個Optional 例項,value 可能是空,也可能非空
  • 判斷 Optional容器中是否包含物件:
    • boolean isPresent() : 判斷 Optional 容器中的值是否存在
    • void ifPresent(Consumer<? super T> consumer) :判斷 Optional 容器中的值是否存在,如果存在,就對它進行 Consumer 指定的操作,如果不存在就不做
  • 獲取Optional 容器的物件:
  • T get(): 如果呼叫物件包含值,返回該值。否則拋異常。T get()與 of(T value)配合使用
  • T orElse(T other):orElse(T other) 與 ofNullable(T value)配合使用,如果Optional 容器中非空,就返回所包裝值,如果為空,就用 orElse(T other)other 指定的預設值(備胎)代
  • T orElseGet(Supplier<? extends T> other) :如果 Optional 容器中非空,就返回所包裝值,如果為空,就用 Supplier 介面的 Lambda 表示式提供的值代替
  • T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果 Optional 容器中非空,就返回所包裝值,如果為空,就丟擲你指定的異常型別代替原來的NoSuchElementException

舉例:略

這是 JDK9-11 的新特性

新增方法 描述 新增的版本
boolean isEmpty() 判斷 value 是否為空 JDK 11
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) value 非空,執行引數 1 功能;如果 value 為空,執行引數 2 功能 JDK 9
Optional or(Supplier<? extends Optional<? extends T>> supplier) value 非空,返回對應的Optional;value 為空,返回形參封裝的 Optional JDK 9
Stream stream() value 非空,返回僅包含此 value的 Stream;否則,返回一個空的Stream JDK 9
T orElseThrow() value 非空,返回 value;否則拋異常 NoSuchElementException JDK 10

7.2 String 儲存結構和 API 變更

這是 JDK9 的新特性。

產生背景:

Motivation The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

使用說明:

Description

We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.

結論:String 再也不用 char[] 來儲存啦,改成了 byte[] 加上編碼標記,節約了一些空間。

public final class String
 implements java.io.Serializable, Comparable<String>, CharSequence
{
     @Stable
     private final byte[] value;
    ...
}

擴充:StringBuffer 與 StringBuilder

那 StringBuffer 和 StringBuilder 是否仍無動於衷呢?

String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM's intrinsic string operations.

JDK11 新特性:新增了一系列字串處理方法

描述 舉例
判斷字串是否為空白 "".isBlank(); // true
去除首尾空白 " Javastack ".strip(); // "Javastack"
去除尾部空格 " Javastack ".stripTrailing(); // " Javastack"
去除首部空格 " Javastack ".stripLeading(); // "Javastack "
複製字串 "Java".repeat(3);// "JavaJavaJava"
行數統計 "A\nB\nC".lines().count(); // 3

JDK12 新特性:String 實現了 Constable 介面

//String 原始碼:
public final class String implements java.io.Serializable, Comparable
<String>, CharSequence,Constable, ConstantDesc {
//java.lang.constant.Constable 介面定義了抽象方法:
public interface Constable {
	Optional<? extends ConstantDesc> describeConstable();
}

Java 12 String 的實現原始碼:

/**
* Returns an {@link Optional} containing the nominal descriptor for 
this
* instance, which is the instance itself.
*
* @return an {@link Optional} describing the {@linkplain String} ins
tance
* @since 12
*/
@Override
public Optional<String> describeConstable() {
	return Optional.of(this);
}

很簡單,其實就是呼叫 Optional.of 方法返回一個 Optional 型別

舉例:

private static void testDescribeConstable() {
    String name = "Java 高階工程師";
    Optional<String> optional = name.describeConstable();
    System.out.println(optional.get());
}

結果輸出:

Java 高階工程師

JDK12 新特性:String 新增方法

String 的 transform(Function)
var result = "foo".transform(input -> input + " bar");
System.out.println(result); *//foo bar*
或者
var result = "foo".transform(input -> input + " bar").transform(String::toUpperCase)
System.out.println(result); //FOO BAR
//對應的原始碼:
/**
* This method allows the application of a function to {@code this}
* string. The function should expect a single String argument
* and produce an {@code R} result.
* @since 12
*/
public <R> R transform(Function<? super String, ? extends R> f) {
	return f.apply(this);
}

在某種情況下,該方法應該被稱為 map()。

//舉例:
private static void testTransform() {
    System.out.println("======test java 12 transform======");
    List<String> list1 = List.of("Java", " Python", " C++ ");
    List<String> list2 = new ArrayList<>();
    list1.forEach(element -> list2.add(element.transform(String::strip)
     .transform(String::toUpperCase)
     .transform((e) -> "Hi," + e))
    );
    list2.forEach(System.out::println);
}s

結果輸出:

test java 12 transform

Hi,JAVA

Hi,PYTHON

Hi,C++

如果使用 Java 8 的 Stream 特性,可以如下實現:

private static void testTransform1() {
    System.out.println("======test before java 12 ======");
    List<String> list1 = List.of("Java ", " Python", " C++ ");
    Stream<String> stringStream = list1.stream()
        .map(element -> element.strip())
        .map(String::toUpperCase)
        .map(element -> "Hello," + element);
    List<String> list2 = stringStream.collect(Collectors.toList());
    list2.forEach(System.out::println);
 }

7.3 JDK17:標記刪除 Applet API

Applet API 提供了一種將 Java AWT/Swing 控制元件嵌入到瀏覽器網頁中的方法。不過,目前 Applet 已經被淘汰。大部分人可能壓根就沒有用過 Applet。Applet API 實際上是無用的,因為所有 Web 瀏覽器供應商都已刪除或透露計劃放棄對 Java 瀏覽器外掛的支援。Java 9 的時候,Applet API 已經被標記為過時,Java 17 的時候終於標記為刪除了。

具體如下:

java.applet.Applet

java.applet.AppletStub

java.applet.AppletContext

java.applet.AudioClip

javax.swing.JApplet

java.beans.AppletInitializer

8. 其它結構變化

8.1 JDK9:UnderScore(下劃線)使用的限制

在 java 8 中,識別符號可以獨立使用“_”來命名:

String _ = "hello";

System.out.println(_);

但是,在 java 9 中規定“_”不再可以單獨命名識別符號了,如果使用,會報錯:

8.2 JDK11:更簡化的編譯執行程式

看下面的程式碼。

javac JavaStack.java // 編譯

java JavaStack //執行

我們的認知裡,要執行一個 Java 原始碼必須先編譯,再執行。而在 Java 11 版本中,透過一個 java 命令就直接搞定了,如下所示:

java JavaStack.java

注意點:

執行原始檔中的第一個類,第一個類必須包含主方法。

8.3 GC 方面新特性 (JVM相關知識)

GC 是 Java 主要優勢之一。 然而,當 GC 停頓太長,就會開始影響應用的響應時間。隨著現代系統中記憶體不斷增長,使用者和程式設計師希望 JVM 能夠以高效的方式充分利用這些記憶體, 並且無需長時間的 GC 暫停時間。

8.3.1 G1 GC

JDK9 以後預設的垃圾回收器是 G1GC。

JDK10 : 為 G1 提供並行的 Full GC

G1 最大的亮點就是可以儘量的避免 full gc。但畢竟是“儘量”,在有些情況下,G1 就要進行 full gc 了,比如如果它無法足夠快的回收記憶體的時候,它就會強制停止所有的應用執行緒然後清理。

在 Java10 之前,一個單執行緒版的標記-清除-壓縮演算法被用於 full gc。為了儘量減少 full gc 帶來的影響,在 Java10 中,就把之前的那個單執行緒版的標記-清除-

壓縮的 full gc 演算法改成了支援多個執行緒同時 full gc。這樣也算是減少了 full gc所帶來的停頓,從而提高效能。

你可以透過-XX:ParallelGCThreads 引數來指定用於並行 GC 的執行緒數。

JDK12:可中斷的 G1 Mixed GC

JDK12:增強 G1,自動返回未用堆記憶體給作業系統

8.3.2 Shenandoah GC

JDK12:Shenandoah GC:低停頓時間的 GC

Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣佈進行的一項垃圾收集器研究專案 Pauseless GC 的實現,旨在針對 JVM 上的記憶體收回實現低停頓的需求。據 Red Hat 研發 Shenandoah 團隊對外宣稱,Shenandoah 垃圾回收器的暫停時間與堆大小無關,這意味著無論將堆設定為 200 MB 還是 200 GB,

都將擁有一致的系統暫停時間,不過實際使用效能將取決於實際工作堆的大小和工作負載。

Shenandoah GC 主要目標是 99.9% 的暫停小於 10ms,暫停與堆大小無關等。這是一個實驗性功能,不包含在預設(Oracle)的 OpenJDK 版本中。

JDK15:Shenandoah 垃圾回收演算法轉正

Shenandoah 垃圾回收演算法終於從實驗特性轉變為產品特性,這是一個從 JDK 12 引入的回收演算法,該演算法透過與正在執行的 Java 執行緒同時進行疏散工作來減少 GC 暫停時間。Shenandoah 的暫停時間與堆大小無關,無論堆疊是 200 MB 還是 200 GB,都具有相同的一致暫停時間。

Shenandoah 在 JDK12 被作為 experimental 引入,在 JDK15 變為 Production;之前需要透過-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC 來啟用,現在只需要-XX:+UseShenandoahGC 即可啟用。

8.3.3 革命性的 ZGC

JDK11:引入革命性的 ZGC

ZGC,這應該是 JDK11 最為矚目的特性,沒有之一。

ZGC 是一個併發、基於 region、壓縮型的垃圾收集器。

ZGC 的設計目標是:支援 TB 級記憶體容量,暫停時間低(<10ms),對整個程式吞吐量的影響小於 15%。 將來還可以擴充套件實現機制,以支援不少令人興奮的功能,例如多層堆(即熱物件置於 DRAM 和冷物件置於 NVMe 快閃記憶體),或壓縮堆。

JDK13:ZGC:將未使用的堆記憶體歸還給作業系統

JDK14:ZGC on macOS 和 windows

  • JDK14 之前,ZGC 僅 Linux 才支援。現在 mac 或 Windows 上也能使用 ZGC 了,示例如下:
    • -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
  • ZGC 與 Shenandoah 目標高度相似,在儘可能對吞吐量影響不大的前提下,實現在任意堆記憶體大小下都可以把垃圾收集的停頓時間限制在十毫秒以內的低延遲。

JDK15:ZGC 功能轉正

ZGC 是 Java 11 引入的新的垃圾收集器,經過了多個實驗階段,自此終於成為正式特性。

但是這並不是替換預設的 GC,預設的 GC 仍然還是 G1;之前需要透過-XX:+UnlockExperimentalVMOptions、-XX:+UseZGC 來啟用 ZGC,現在只需要-XX:+UseZGC 就可以。相信不久的將來它必將成為預設的垃圾回收器。ZGC 的效能已經相當亮眼,用“令人震驚、革命性”來形容,不為過。未來將成為服務端、大記憶體、低延遲應用的首選垃圾收集器。

怎麼形容 Shenandoah 和 ZGC 的關係呢?異同點大概如下:

  • 相同點:效能幾乎可認為是相同的
  • 不同點:ZGC 是 Oracle JDK 的,根正苗紅。而 Shenandoah 只存在於 OpenJDK 中,因此使用時需注意你的 JDK 版本

JDK16:ZGC 併發執行緒處理

線上程的堆疊處理過程中,總有一個制約因素就是 safepoints。在 safepoints 這個點,Java 的執行緒是要暫停執行的,從而限制了 GC 的效率。

回顧:

我們都知道,在之前,需要 GC 的時候,為了進行垃圾回收,需要所有的執行緒都暫停下來,這個暫停的時間我們稱為 Stop The World。而為了實現 STW 這個操作, JVM 需要為每個執行緒選擇一個點停止執行,這個點就叫做安全點(Safepoints)。而 ZGC 的併發執行緒堆疊處理可以保證 Java 執行緒可以在 GC safepoints 的同時可以併發執行。它有助於提高所開發的 Java 軟體應用程式的效能和效率。

9. 小結與展望

隨著雲端計算和 AI 等技術浪潮,當前的計算模式和場景正在發生翻天覆地的變化,不僅對 Java 的發展速度提出了更高要求,也深刻影響著 Java 技術的發展方向。傳統的大型企業或網際網路應用,正在被雲端、容器化應用、模組化的微服務甚至是函式(FaaS, Function-as-a-Service)所替代。

Java 需要在新的計算場景下,改進開發效率。比如,Java 程式碼雖然進行了一些型別推斷等改進,更易用的集合 API 等,但仍然給開發者留下了過於刻板、形式主義的印象,這是一個長期的改進方向。

Java 雖然標榜物件導向程式設計,卻毫不顧忌的加入面向介面程式設計思想,又扯出匿名的概念,每增加一個新的東西,對 Java 的根本(物件導向思想)的一次衝擊。

只是為了記錄自己的學習歷程,且本人水平有限,不對之處,請指正。