來了來了,Java14它真的來了

在這裏插入圖片描述

關於麥洛

Michael Scharhag
麥洛是 Java 開發者和技術愛好者。 對 Java 相關技術特別感興趣,包括 javaee、 Spring系列、 微服務等

語言必須發展,否則它們就有變得無關緊要的風險,” Brian Goetz (甲骨文公司)在2019年11月在 Devoxx 舉行的“ Java 語言期貨”演講中說。 作爲 Java 語言架構師,他扮演了一個重要的角色,儘管 Java 已經發展了25年,但仍然遠遠沒有過時。 在本文中,我們將研究 JDK 14的創新

作者: Falk Sippach

文章出處:Java – the fourteenth

近年來,甲骨文做出了一些突破性的決定。 他們包括新的半年發佈模式與預覽功能和更短的發佈和反饋週期的新功能。 許可模式也發生了變化,Oracle JDK 不再免費提供。 這加劇了競爭,因此您現在可以從包括 Oracle 在內的各種供應商獲得免費的 OpenJDK 發行版。 自從 java11以來,它已經與 oraclejdk 實現了二進制兼容,並且採用開源許可證。

一年半以前,最後一個 LTS 版本 Java 11於2019年秋季發佈。 從那時起,隨後的兩個主要版本只有有限數量的新特性。 JDK 孵化器項目(Amber, Valhalla, Loom …) ,然而,正在致力於許多新想法,所以不足爲奇的是,剛剛發佈的 JDK 14的功能範圍再次顯著擴大。 即使只有少數人會在生產環境中使用新版本,您仍然應該關注新特性,並儘早就預覽功能提供反饋。 這是確保新特性投入生產直到下一個 LTS 版本最終完成的唯一方法,該版本將在2021年秋季以 Java 17的形式發佈。

下列Java增強建議(JEP)已經實現。在本文中,我們將從開發人員的角度來仔細研究感興趣的主題。

  • JEP 305: Pattern Matching for instanceof (Preview)
  • JEP 343: Packaging Tool (Incubator)
  • JEP 345: NUMA-Aware Memory Allocation for G1
  • JEP 349: JFR Event Streaming
  • JEP 352: Non-Volatile Mapped Byte Buffers
  • JEP 358: Helpful NullPointerExceptions
  • JEP 359: Records (Preview)
  • JEP 361: Switch Expressions (Standard)
  • JEP 362: Deprecate the Solaris and SPARC Ports
  • JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
  • JEP 364: ZGC on macOS
  • JEP 365: ZGC on Windows
  • JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination
  • JEP 367: Remove the Pack200 Tools and API
  • JEP 368: Text Blocks (Second Preview)
  • JEP 370: Foreign-Memory Access API (Incubator)

JEP 305: Pattern matching for instanceof

自20世紀60年代以來,模式匹配語言的概念已經在各種編程語言中得到了應用。 其中最現代的例子是 Haskell 和 Scala。 模式是一個謂詞的組合,該謂詞匹配目標結構和該模式中的一組變量。 如果這些變量匹配,則爲它們分配相應的內容。 其目的是破壞對象,也就是將它們分解爲它們的組件。

到目前爲止,Java 只能區分 switch 語句中的數據類型 integerstringenum。 然而,隨着 Java 12中開關表達式的引入,邁向模式匹配的第一步已經邁出。 使用 Java 14,我們現在可以額外地使用模式匹配操作符 instanceof。 避免了不必要的強制轉換,減少的冗餘提高了可讀性。

例如,在此之前,必須按照以下步驟檢查空字符串或空集合:

boolean isNullOrEmpty( Object o ) {
  return == null ||
    instanceof String && ((String) o).isBlank() ||
    instanceof Collection && ((Collection) o).isEmpty();
}

現在您可以在使用 instanceof 檢查時直接將值賦給變量,並對其執行進一步調用:

boolean isNullOrEmpty( Object o ) {
  return o == null ||
    o instanceof String s && s.isBlank() ||
    o instanceof Collection c && c.isEmpty();
}

這種區別似乎微不足道,然而,Java 開發人員中的純粹主義者節省了一個小而煩人的冗餘。

開關表達式最早是在 Java 12和13中引入的,在這兩種情況下都是作爲一個預覽特性。 它們現已在 jep361中最後確定。 這爲開發人員提供了兩種新的語法變體,它們具有更短、更清晰和更不容易出錯的語義。 表達式的結果可以分配給變量,或者作爲方法的值返回(清單1)。

JEP 358: Helpful NullPointerExceptions

對空引用的無意訪問也是 Java 開發人員所擔心的。 根據託尼•霍爾爵士(Sir Tony Hoare)自己的說法,他發明的零Y引用是一個錯誤,其後果高達數十億美元。 這僅僅是因爲在20世紀60年代阿爾戈語的發展過程中,它是如此容易實現。

在 Java 中,編譯器和運行時環境都不支持處理零引用。 這些惱人的異常可以通過各種變通方法來避免。 最簡單的方法是將檢查設置爲零。 不幸的是,這個過程非常繁瑣,當我們需要它的時候我們往往會忘記它。 使用自 JDK 8以來包含的包裝器類 Optional,您可以通過 API 顯式地告訴調用者,一個值可以爲零,並且它必須對此進行響應。 因此,您不能再意外地遇到空引用,而必須顯式地處理可能爲空的值。 這個過程對於公共接口的返回值非常有用,但是也會消耗額外的間接層,因爲您總是需要解壓實際值。

在其他語言中,輔助工具早已構建到語法和編譯器中,比如Groovy中 NullObjectPatternSafe Navigation 操作符(some?.method())。 在 Kotlin,可以明確區分可能不爲空的類型和可能作爲引用爲 null 的類型。 我們將來也必須使用 Java 中的 nullpointerexception。 但是,作爲預覽特性引入的有用的NullPointerExceptions可以簡化異常的故障排除。 爲了在拋出 NullPointerException 時插入必要的信息,必須在啓動時激活選項 -XX: + ShowCodeDetailsInExceptionMessages。 如果調用鏈中的一個值爲零,那麼您將收到一條有用的消息:

man.partner().name()
 
Result: java.lang.NullPointerException: Cannot invoke "Person.name()" because the return value of "Person.partner()" is null

Lambda表達式需要特殊處理。 例如,如果lambda函數的參數爲零,則默認情況下將收到清單2所示的錯誤消息。要顯示正確的參數名稱,必須使用-g:vars選項編譯源代碼。 結果如下:

java.lang.NullPointerException: Cannot invoke "Person.name()" because "p" is null

Listing 2 清單2
Stream.of( man, woman )
  .map( p -> p.partner() )
.map( p -> p.name() )
.collect( Collectors.toUnmodifiableList() );
 
Result: java.lang.NullPointerException: Cannot invoke "Person.name()" because "<parameter1>" is null

不幸的是,當一個空參數時,目前沒有方法引用的指示:

Stream.of( man, woman )
  .map( Person::partner )
  .map( Person::name )
  .collect( Collectors.toUnmodifiableList() )
Result: java.lang.NullPointerException

但是,如本例所示,如果將每個流方法調用放在新行中,那麼麻煩的代碼行可以很快地縮小範圍。 NullPointerExceptions在自動裝箱/拆箱中也具有挑戰性。 如果在這裏也激活了編譯器參數-g:vars,您還將收到新的有用的錯誤消息(清單3)。

Listing 3 清單3
int calculate() {
  Integer a = 2, b = 4, x = null;
  return a + b * x;
}
calculate();
Result: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "x" is null

JEP 359: Records

也許最令人興奮的同時也是最令人驚訝的創新是記錄類型的引入 。 它們是在Java 14發行中相對較晚實現的,是一種類聲明的限制形式,類似於枚舉。 記錄是在Valhalla項目中開發的。 與Kotlin中的Data ClassesScala中的Case Classes有某些相似之處。 緊湊的語法可能會使Lombok之類的庫在將來過時。 Kevlin Henney還看到了以下優點:“我認爲Java記錄功能的有趣的副作用之一是,實際上,它將幫助揭示多少Java代碼實際上是面向 getter / setter而非面向對象的。” 一個人有兩個字段的簡單定義可以在這裏看到 :

public record Person( String name, Person partner ) {}

一個帶有附加構造函數的擴展變量,因此只有字段*name* 是強制的,也可以實現:

public record Person( String name, Person partner ) {
  public Person( String name ) { this( name, null ); }
  public String getNameInUppercase() { return name.toUpperCase(); }
}

編譯器生成一個不可變類,除了這兩個屬性和它自己的方法之外,它還包含訪問器的實現(沒有 getter!) 、構造函數 equals / hashcode 和 toString (清單4)。

Listing 4 清單4
public final class Person extends Record {
  private final String name;
  private final Person partner;
   
  public Person(String name) { this(name, null); }
  public Person(String name, Person partner) { this.name = name; this.partner = partner; }
 
  public String getNameInUppercase() { return name.toUpperCase(); }
  public String toString() { /* ... */ }
  public final int hashCode() { /* ... */ }
  public final boolean equals(Object o) { /* ... */ }
  public String name() { return name; }
  public Person partner() { return partner; }
}

使用的行爲符合預期,您無法從調用方判斷記錄類型是實例化的(清單5)。

Listing 5 清單5
var man = new Person("Adam");
var woman = new Person("Eve", man);
woman.toString(); // ==> "Person[name=Eve, partner=Person[name=Adam, partner=null]]"
 
woman.partner().name(); // ==> "Adam"
woman.getNameInUppercase(); // ==> "EVE"
 
// Deep equals
new Person("Eve", new Person("Adam")).equals( woman ); // ==> true

順便說一下,records 不是經典的 javabean,因爲它們不包含真正的 getter。 但是,您可以使用相同名稱的方法訪問成員變量。 記錄也可以包含註釋或 Javadocs。 此外,還可以在主體中聲明靜態字段、方法、構造函數或實例方法。 不允許在記錄頭之外定義其他實例字段。

JEP 368: Text Blocks

最初計劃作爲 java12的原始字符串,java13引入了一個更輕量級的版本,稱爲文本塊的多行字符串的形式。 特別是對於 HTML 模板和 SQL 腳本,它們極大地提高了可讀性(清單6)。

Listing 6 清單6

// 未使用 Text Blocks
String html = "<html>\n" +
              "    <body>\n" +
              "        
 
Hello, Escapes
 
\n" +
              "    </body>\n" +
              "</html>\n";
 
// With Text Blocks
String html = """
              <html>
                  <body>
                       
 
Hello, Text Blocks
 
                  </body>
              </html>""";Listing 6 清單6

// Without Text Blocks
String html = "<html>\n" +
              "    <body>\n" +
              "        
 
Hello, Escapes
 
\n" +
              "    </body>\n" +
              "</html>\n";
 
// 使用 Text Blocks
String html = """
              <html>
                  <body>
                       
 
Hello, Text Blocks
 
                  </body>
              </html>""";

還添加了兩個新的轉義序列,您可以使用它們調整文本塊的格式。 例如,如果要使用不應在輸出中顯式出現的換行符,則只需在行尾插入\(反斜槓)即可。 這爲您提供了一個帶有長行的字符串,但是爲了清楚起見,您可以在源代碼中使用換行符(清單7)。

Listing 7 清單7

String text = """
                Lorem ipsum dolor sit amet, consectetur adipiscing \
                elit, sed do eiusmod tempor incididunt ut labore \
                et dolore magna aliqua.\
                """;
// 代替+
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
                 "elit, sed do eiusmod tempor incididunt ut labore " +
                 "et dolore magna aliqua.";

新的轉義序列\ s被轉換爲空格。 例如,這可以用於確保行尾的空白不會被自動截斷(修剪),並獲得每行固定的字符寬度:

String colors = """
    red  \s
    green\s
    blue \s
    """;

還有什麼新鮮事嗎?

除了所描述的功能(對於開發人員來說主要是有趣的)之外,還有其他一些更改。 在JEP 352中,對FileChannel API進行了擴展,以允許創建MappedByteBuffer實例。 與易失性存儲器(RAM)不同,它們在非易失性數據存儲(NVM,非易失性存儲器)上工作。 但是,目標平臺是Linux x64。 關於垃圾收集也發生了很多事情。 併發標記掃描(CMS)垃圾收集器已被刪除。 因此,ZGC現在也可用於macOSWindows

對於關鍵的Java應用程序,建議在生產中激活飛行記錄功能。 以下命令使用Flight Recording啓動Java應用程序,並將信息寫入record.jfr,並始終保留一天的數據:

java \
-XX:+FlightRecorder \
-XX:StartFlightRecording=disk=true, \
filename=recording.jfr,dumponexit=true,maxage=1d \
-jar application.jar

通常,您隨後可以使用工具JDK Mission Control(JMC)讀取和分析數據。 JDK 14的另一個新功能是,您還可以從應用程序異步查詢事件(清單8)。

Listing 8 清單8
import jdk.jfr.consumer.RecordingStream;
import java.time.Duration;
 
try ( var rs = new RecordingStream() ) {
  rs.enable( "jdk.CPULoad" ).withPeriod( Duration.ofSeconds( 1 ) );
  rs.onEvent( "jdk.CPULoad", event -> {
    System.out.printf( "%.1f %% %n", event.getFloat( "machineTotal" ) * 100 );
  });
  rs.start();
}

JDK 8中,我們擁有工具javapackager,但是不幸的是,它在版本11中與JavaFX一起從Java中刪除。 現在,在Java 14中引入了後繼jpackageJEP 343:打包工具),利用它我們可以再次創建獨立的Java安裝文件。 它們的基礎是包括運行時環境的Java應用程序。 該工具使用此輸入來構建包含所有依賴項的可執行二進制工件(格式:dmg中的msiexepkgdmg中的appdebrpm)。

總結

Java沒有死,Java萬歲! 半年兩次的OpenJDK版本使語言和平臺都受益。 這次,新功能比Java 12和13還要多。而且,仍有許多功能需要在將來的版本中實現。 因此,我們的Java開發人員不會感到無聊,並且未來的前景仍然一片光明。 到2020年9月,我們可以預見Java 15的到來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章