Java 16 新特性介紹

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"本文要點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16和即將發佈的Java 17引入了大量特性和語言增強,有助於提高開發人員的生產力和應用程序性能"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16 Stream API爲常用的終端操作提供了很多新方法,有助於減少樣板代碼的混亂現象"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Record是Java 16中的一項語言新特性,可簡潔地定義純數據類。編譯器提供了構造器、訪問器和一些常見Object方法的實現"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"模式匹配是Java 16中的另一個新特性,它簡化了使用instanceof代碼塊完成的顯式和冗長的轉換,此外還有很多好處"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16於2021年3月發佈,版本類型是可用於生產的GA構建,我在這段"},{"type":"link","attrs":{"href":"http:\/\/www.infoq.com\/presentations\/new-java-16\/","title":null,"type":null},"content":[{"type":"text","text":"深度視頻演示"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"中介紹了該版本的新特性。下一個LTS版本Java 17計劃於今年9月發佈。Java 17將包含許多改進和語言增強,其中大部分是自Java 11以來交付的所有新特性和更改的成果結晶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"就Java 16中的新特性而言,我將分享Stream API中一項討喜的更新,然後主要關注語言更改部分。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"從Stream到List"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"List features =\n Stream.of(\"Records\", \"Pattern Matching\", \"Sealed Classes\")\n .map(String::toLowerCase)\n .filter(s -> s.contains(\" \"))\n .collect(Collectors.toList());"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果你習慣使用Java Stream API,那麼應該會很熟悉上面這個代碼段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這段代碼裏有一個包含一些字符串的流。我們在它上面映射一個函數,然後過濾這個流。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"最後,我們將流物化爲一個列表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如你所見,我們通常會調用終端操作collect並給它傳遞一個收集器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這裏是很常見的實踐——使用collect並將Collectors.toList()傳遞給它,感覺就像是樣板代碼一樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"好消息是,在Java 16中Stream API中添加了一個新方法,使我們能夠立即將toList()作爲一個流的一個終端操作來調用。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"List features =\n Stream.of(\"Records\", \"Pattern Matching\", \"Sealed Classes\")\n .map(String::toLowerCase)\n .filter(s -> s.contains(\" \"))\n .toList();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在上面的代碼中使用這個新方法會生成一個來自這個流,且包含一個空格的字符串的列表。請注意,我們返回的這個列表是一個不可修改的列表。這意味着你不能再從這個終端操作返回的列表中添加或刪除任何元素。如果要將流收集到一個可變列表中,則必須繼續使用一個帶有collect()函數的收集器。所以這個Java 16中新引入的toList()方法真的很討喜。這個更新應該能讓流管道代碼塊讀起來更容易一些。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Stream API的另一個更新是mapMulti()方法。它的用途有點像flatMap()方法。如果你平常用的是flatMap(),並且映射到lambda中的內部流並傳遞給它,那麼mapMulti()爲你提供了一種替代方法,你可以將元素推送給一個消費者。我不會在本文中具體介紹這個方法,因爲我想討論的是Java 16中的語言新特性。如果你有興趣進一步瞭解mapMulti(),我強烈建議你查看"},{"type":"link","attrs":{"href":"https:\/\/docs.oracle.com\/en\/java\/javase\/16\/docs\/api\/java.base\/java\/util\/stream\/Stream.html#mapMulti(java.util.function.BiConsumer)","title":null,"type":null},"content":[{"type":"text","text":"Java文檔"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"中關於這種方法的介紹。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Records"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16中引入的第一個重大語言特性稱爲記錄(records)。記錄用來將數據表示爲Java代碼中的數據,而不是任意類。在Java 16之前,有時我們只是需要表示一些數據,最終卻得到了一個任意類,如下面的代碼段所示。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class Product {\n private String name;\n private String vendor;\n Private int price;\n private boolean inStock;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這裏我們有一個Product類,它有四個成員。定義這個類所需的所有信息應該就是這些了。當然,我們需要更多的代碼來完成這項工作。例如,我們需要有一個構造器。我們需要有相應的getter方法來獲取成員的值。爲了補充完整,我們還需要有與我們定義的成員一致的equals()、hashCode()和toString()實現。一些樣板代碼可以由IDE生成,但這樣做會有一些缺陷。你也可以使用Lombok等框架,但它們也有一些缺陷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們真正需要的是由Java語言提供一種機制,可以更準確地描述擁有純數據類這個概念。所以在Java 16中我們有了記錄的概念。在以下代碼段中,我們將Product類重新定義爲一個記錄。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public record Product(\n String name,\n String vendor,\n int price,\n boolean inStock) {\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"請注意這裏引入了新關鍵字record。我們需要在關鍵字record之後指定記錄類型的名稱。在我們的示例中,這個名稱是Product。然後我們只需要提供組成這些記錄的組件。在這裏,我們給出了四個組件的類型和名稱以提供它們。然後我們就完成了。Java中的記錄是類的一個特殊形式,其中只包含數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"記錄能給我們帶來什麼呢?一旦我們有了一個記錄聲明,我們就會得到一個類,它有一個隱式構造器,接受這個記錄的組件的所有值。我們會根據所有記錄組件自動獲取equals()、hashCode()和toString()方法的實現。此外,我們還爲記錄中的每個組件獲取訪問器方法。在上面的例子中,我們得到了一個name方法、一個vendor方法、一個price方法和一個inStock方法,它們分別返回這個記錄的組件的實際值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"記錄永遠是不可變的。這裏沒有setter方法。一旦使用某些值實例化一個記錄,那麼你就無法再更改它了。此外,記錄類就是最終形式。你可以使用一個記錄實現一個接口,但在定義記錄時不能擴展其他任何類。總而言之,這裏有一些限制。但是記錄爲我們提供了一種非常強大的方式來在我們的應用程序中簡潔地定義純數據類。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"怎樣看待記錄"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你應該如何看待和處理這些新的語言元素呢?記錄是一種新的、受限形式的類,用於將數據建模爲數據。我們不可能向記錄添加任何附加狀態;除了記錄的組件之外,你不能定義(非靜態)字段。記錄實際上是建模不可變數據的。你也可以將記錄視爲元組,但它並不只是其他一些語言所有的那種一般意義上的元組,在那種元組裏有一些可以由索引引用的任意組件。在Java中,元組元素有實際名稱,並且元組類型本身——即記錄,也有一個名稱,因爲名稱在Java中很重要。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"記錄不適合哪些場景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"有些場景中我們可能會覺得記錄用起來並不是很合適。首先,它們並不是任何現有代碼的一個樣板縮減機制。雖然我們現在有一種非常簡潔的方式來定義這些記錄,但這並不意味着你的應用程序中的任何數據(如類)都可以輕鬆地被記錄替換,這主要是因爲記錄存在的一些限制所致。這也不是它真正的設計目標。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"記錄的設計目標是提供一種將數據建模爲數據的好方法。它也不是JavaBeans的直接替代品,因爲正如我之前提到的,訪問器這樣的方法不符合JavaBeans的get標準。另外JavaBeans通常是可變的,而記錄是不可變的。儘管它們的用途有點像,但記錄並不會以某種方式取代JavaBean。你也不應該將記錄視爲值類型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"值類型可能會在未來的Java版本中作爲語言增強引入,其主要關注內存佈局和類中數據的有效表示。當然,這兩條世界線在未來某一時刻可能會合並在一起,但就目前而言,記錄只是表達純數據類的一種更簡潔的方式。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"進一步瞭解記錄"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"考慮以下代碼,我們創建了Product類型的記錄p1和p2,具有完全相同的值。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Product p1 = new Product(\"peanut butter\", \"my-vendor\", 20, true);\nProduct p2 = new Product(\"peanut butter\", \"my-vendor\", 20, true);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們可以通過引用相等來比較這些記錄,也可以使用equals()方法比較它們,該方法已由記錄實現自動提供。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"System.out.println(p1 == p2); \/\/ Prints false\nSystem.out.println(p1.equals(p2)); \/\/ Prints true"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"可以看到,這兩條記錄是兩個不同的實例,因此引用對比將給出false。但是當我們使用equals()時,它只查看這兩個記錄的值,所以它會評估爲true。因爲它只考慮記錄內部的數據。重申一下,相等性和哈希碼的實現完全基於我們爲記錄的構造器提供的值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"需要注意的一件事是,你仍然可以覆蓋記錄定義中的任何訪問器方法,或者相等性和哈希碼實現。但是,你有責任在記錄的上下文中保留這些方法的語義。並且你可以向記錄定義添加其他方法。你還可以訪問這些新方法中的記錄值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一個你可能想在記錄中執行的重要特性是驗證。例如,你只想在提供給記錄構造器的輸入有效時才創建記錄。傳統的驗證方法是定義一個帶有輸入參數的構造器,這些參數在將參數分配給成員變量之前進行驗證。但是對於記錄而言,我們可以使用一種新格式,即所謂的緊湊構造器。在這種格式中,我們可以省略正式的構造器參數。構造器將隱式地訪問組件值。在我們的Product示例中,我們可以說如果price小於零,則拋出一個新的IllegalArgumentException。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public record Product(\n String name,\n String vendor,\n int price,\n boolean inStock) {\n public Product {\n if (price < 0) {\n throw new IllegalArgumentException();\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從上面的代碼段中可以看出,如果價格高於零,我們就不必手動做任何賦值。在編譯此記錄時,編譯器會自動添加從(隱式)構造器參數到記錄字段的賦值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果我們願意,甚至可以進行正則化。例如,我們可以將隱式可用的價格參數設置爲一個默認值,而不是在價格小於零時拋出異常。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public Product {\n if (price < 0) {\n price = 100;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"同樣,對記錄的實際成員的賦值——即作爲這個記錄定義一部分的最終字段,是由編譯器在這個緊湊構造器的末尾自動插入的。總而言之,這是在Java中定義純數據類的一種非常通用且非常棒的方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你還可以在方法中本地聲明和定義記錄。如果你想在方法中使用一些中間狀態,這會非常方便。例如,假設我們要定義一個打折產品。我們可以定義一個記錄,包含Product和一個指示產品是否打折的boolean值。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String... args) {\n Product p1 = new Product(\"peanut butter\", \"my-vendor\", 100, true);\n record DiscountedProduct(Product product, boolean discounted) {}\n System.out.println(new DiscountedProduct(p1, true));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從上面的代碼段中可以看出,我們不必爲新記錄定義提供正文。我們可以使用p1和true作爲參數來實例化DiscountedProduct。運行代碼時,你會看到它的行爲方式與源文件中的頂級記錄完全相同。如果你希望在流管道的中間階段分組某些數據,作爲本地構造的記錄會非常有用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"你會在哪裏使用記錄"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"記錄有一些顯而易見的使用場景。比如說當我們想要使用數據傳輸對象(Data Transfer Objects,DTO)時就可以使用記錄。根據定義,DTO是不需要任何身份或行爲的對象。它們只是用來傳輸數據的。例如,從2.12版本開始,Jackson庫支持將記錄序列化和反序列化爲JSON和其他支持的格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果你希望一個映射中的鍵由充當複合鍵的多個值組成,記錄也會很好用,因爲你會自動獲得equals和hashcode實現的正確行爲。由於記錄也可以被認爲是名義元組(其中每個組件都有一個名稱),使用記錄將多個值從方法返回給調用者也是很方便的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一方面,我認爲記錄在Java Persistence API中用的不會很多。如果你想使用記錄來表示實體,那實際上是不可能的,因爲實體在很大程度上是基於JavaBeans約定。並且實體通常傾向於是可變的。當然,當你在查詢中實例化只讀視圖對象時,有些情況下你可以使用記錄代替常規類。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"總而言之,我認爲Java中引入記錄是一項激動人心的改進。我認爲它們會得到廣泛使用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"instanceof的模式匹配"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16中的第二大語言更改是instanceof的模式匹配。這是將模式匹配引入Java的漫長旅程的第一步。就目前而言,我認爲Java 16中提供的初期支持已經很不錯了。看看下面的代碼段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf String) {\n String s = (String) o;\n return s.length();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你可能會認出這種模式,其中一些代碼負責檢查對象是否是一個類型的實例,在本例中是String類。如果檢查通過,我們需要聲明一個新的作用域變量,轉換並賦值,然後我們才能開始使用這個類型化的變量。在這個示例中,我們需要聲明變量s,cast o爲一個String,然後調用length()方法。雖然這種辦法也能用,但太囉嗦了,而且並沒有反映出代碼的真實意圖。我們有更好的辦法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從Java 16開始,我們可以使用新的模式匹配特性了。使用模式匹配時,我們可以將o匹配一個類型模式,而不是說o是一個特定類型的實例。類型模式由一個類型和一個綁定變量組成。我們來看一個例子。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf String s) {\n return s.length();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在上面的代碼段中,如果o確實是String的實例,那麼String s將立即綁定到o的值。這意味着我們可以立即開始使用s作爲一個字符串,而無需在if主體內進行顯式轉換。這裏的另一個好處是s的作用域僅限於if的主體。這裏需要注意的一點是,源代碼中o的類型不應該是String的子類型,因爲如果是這種情況,條件將始終爲真。因此一般而言,如果編譯器檢測到正在測試的對象的類型是模式類型的子類型,則會拋出編譯時錯誤。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一個需要指出的有趣的事情是,編譯器很聰明,可以根據條件的計算結果爲true還是false來推斷s的作用域,正如以下代碼段中所示。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (!(o instanceOf String s)) {\n return 0;\n} else {\n return s.length();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"編譯器看到,如果模式匹配不成功,那麼在else分支中,我們的s將在String類型的作用域內。並且在if分支s不在作用域內時,我們在作用域內就只有o。這種機制稱爲流作用域,其中類型模式變量僅在模式實際匹配時纔在作用域內。這真的很方便,能夠有效簡化這段代碼。你需要注意這個變化,可能需要一點時間來適應。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一個例子裏你也可以清楚地看到這個流的作用。當你重寫equals()方法的以下代碼實現時,常規的實現是首先檢查o是否是MyClass的一個實例。如果是,我們將o轉換爲MyClass,然後將o的name字段與MyClass的當前實例進行匹配。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic boolean equals(Object o) {\n return (o instanceOf MyClass) &&\n ((MyClass) o).name.equals(name);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們可以使用新的模式匹配機制來簡化這個實現,如下面的代碼段所示。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic boolean equals(Object o) {\n return (o instanceOf MyClass m) &&\n m.name.equals(name);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這裏又一次對代碼中顯式、冗長的轉換做了很好的簡化。只要用在合適的用例裏,模式匹配會抽象出許多樣板代碼。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"模式匹配:未來發展"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java團隊已經勾勒出了模式匹配的一些未來發展方向。當然,團隊並沒有承諾這些設想何時或如何引入官方語言。在下面的代碼段中可以看到,在新的switch表達式中,我們可以像之前討論的那樣使用instanceOf來做類型模式。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static String format(Object o) {\n return switch(o) {\n case Integer i -> String.format(\"int %d\", i);\n case Double d -> String.format(\"int %f\", d);\n default -> o.toString();\n };\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在o是整數的情況下,流作用域開始起作用,我們可以立即將變量i用作一個整數。其他情況和默認分支也是如此。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一個令人興奮的新方向是記錄模式,我們可以模式匹配我們的記錄並立即將組件值綁定到新變量。看看下面的代碼段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf Point(int x, int y)) {\n System.out.println(x + y);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們有一個包含x和y的Point記錄。如果對象o確實是一個點,我們將立即將x和y分量綁定到x和y變量並立即開始使用它們。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"數組模式是可能在Java的未來版本中引入的另一種模式匹配。看看下面的代碼段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf String[] {String s1, String s2, ...}) {\n System.out.println(s1 + s2);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果o是字符串數組,則可以立即將這個字符串數組的第一部分和第二部分提取到s1和s2。當然,這隻適用於字符串數組中有兩個或更多元素的情況。我們可以使用三點表示法忽略數組元素的其餘部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"總而言之,使用instanceOf進行模式匹配是一個不錯的小特性,但它是邁向新未來的一步。我們可能會引入其他類型的模式來幫助編寫乾淨、簡單和可讀的代碼。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"特性預覽:密封類"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"下面來談談密封類(sealed class)這個特性。請注意,這是Java 16中的預覽特性,將在Java 17中成爲最終版本。你需要將--enable-preview標誌傳遞給編譯器調用和JVM調用才能在Java 16中使用這個特性。該特性允許你控制繼承層次結構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"假設你想對一個超類型Option建模,其中你只想有Some和Empty兩個子類型。並且你想預防Option類型獲得任何擴展。例如,你不想在層次結構中允許Maybe類型。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/89\/89f67aaf464f67ec066a488747d36570.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"因此,你已經詳細描述了Option類型的所有子類型。如你所知,目前在Java中控制繼承的唯一工具是通過final關鍵字。這意味着根本不能有任何子類,但這不是我們想要的。有一些解決方法可以在沒有密封類的情況下建模這個特性,但有了密封類後就容易多了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"密封類特性帶有新的關鍵字sealed和permits。看看下面的代碼段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public sealed class Option\n permits Some, Empty {\n ...\n}\npublic final class Some\n extends Option {\n ...\n}\npublic final class Empty\n extends Option {\n ...\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們可以定義要sealed的Option類。然後,在類聲明之後,我們使用permit關鍵字來規定只允許Some和Empty類擴展Option類。然後,我們可以像往常一樣將Some和Empty定義爲類。我們希望將這些子類設爲final,以防止進一步繼承。現在系統就不能編譯其他類來擴展Option類了,這是由編譯器通過密封類機制強制執行的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"關於此特性還有很多要說的內容,本文不能一一盡述。如果你有興趣瞭解更多信息,我建議你瀏覽"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/360","title":null,"type":null},"content":[{"type":"text","text":"密封類Java增強提案頁面"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"JEP360。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16中還有很多我們無法在本文中介紹的內容,例如"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/338","title":null,"type":null},"content":[{"type":"text","text":"Vector API"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"、"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/389","title":null,"type":null},"content":[{"type":"text","text":"Foreign Linker API"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"和"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/393","title":null,"type":null},"content":[{"type":"text","text":"Foreign-Memory Access API"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"等孵化器API都非常有前途。並且新版在JVM層面也做了很多改進。例如"},{"type":"link","attrs":{"href":"https:\/\/docs.oracle.com\/en\/java\/javase\/16\/gctuning\/z-garbage-collector.html","title":null,"type":null},"content":[{"type":"text","text":"ZGC"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"有一些性能改進;在JVM中做了一些"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/387","title":null,"type":null},"content":[{"type":"text","text":"Elastic Metaspace"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"改進;還有一個新的Java應用程序"},{"type":"link","attrs":{"href":"https:\/\/docs.oracle.com\/en\/java\/javase\/16\/jpackage\/packaging-overview.html","title":null,"type":null},"content":[{"type":"text","text":"打包工具"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":",允許你爲Windows、Mac和Linux創建原生安裝程序。最後,當你從classpath運行應用程序時,JDK中的封裝類型將受到嚴格保護,我認爲這也會有很大影響。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我強烈建議你研究所有這些新特性和語言增強,因爲其中一些改進會對你的應用程序產生重大影響。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"作者介紹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/twitter.com\/Sander_Mak","title":null,"type":null},"content":[{"type":"text","text":"Sander Mak"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"是一名Java Champion,在Java社區活躍了十多年。目前他是Picnic的技術總監。同時,Mal也經常做知識分享,通過各種會議和在線電子學習平臺傳授經驗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/articles\/java-16-new-features\/","title":null,"type":null},"content":[{"type":"text","text":"What's New in Java 16"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章