Java SE 10 新增特性
Java SE 10 新增特性
作者:Grey
原文地址:Java SE 10 新增特性
源碼
鏡像倉庫: GitCode:java_new_features
類型推斷
無需定義變數類型,通過var
關鍵字結合初始化的值,可以推測出變數類型
package git.snippets.jdk10;
/**
* 類型推斷
*
* @author <a href="mailto:[email protected]">Grey</a>
* @date 2022/8/17
* @since 10
*/
public class TypeRefEnhance {
public static void main(String[] args) {
var a = 2; // a表示int
System.out.println(a);
var b = "hello"; // b 表示String
System.out.println(b);
var date = new java.util.Date();
System.out.println(date);
var obj = new Customer("Grey"); // 自定義對象
System.out.println(obj);
var sum = new TypeRefEnhance().add(1, 23);
System.out.println(sum);
var var = 3;
System.out.println(var);
}
public int add(int a, int b) {
return a + b;
}
static class Customer {
String name;
public Customer(String n) {
name = n;
}
@Override
public String toString() {
return "Customer{" +
"name=" + name +
'}';
}
}
}
var
看似好用,但是請謹慎使用,比如
var x = someFunction()
是因為如果不追蹤someFunction()
方法的返回類型,讀者就不可能知道x的類型。多年來,人們對動態類型語言提出了類似的抱怨。
所以,記住我們的目標是編寫可讀的程式碼。
在Java中,var是一個特殊的新類型,你仍然可以在你的程式碼的其他地方使用var,比如作為一個變數或類名。這使得Java能夠與Java 10之前的程式碼保持向後兼容,所以如下定義是沒問題的
var var = 3;
不可變集合 API
如下程式碼
package git.snippets.jdk10;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 集合API增強
*
* @author <a href="mailto:[email protected]">Grey</a>
* @date 2021/11/29
* @since 10
*/
public class CollectionEnhance {
public static void main(String[] args) {
var vegetables = new ArrayList<>(List.of("Brocolli", "Celery", "Carrot"));
var unmodifiable = Collections.unmodifiableList(vegetables);
vegetables.set(0, "Radish");
var v = unmodifiable.get(0);
// 以下這行會報錯
unmodifiable.set(0, "XXX");
System.out.println(v);
System.out.println(unmodifiable);
}
}
根據Java 10
中Collections
的最新定義,unmodifiableList
返回一個不可修改的視圖集合。所以unmodifiable.set(0, "XXX");
會直接報錯
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.Collections$UnmodifiableList.set(Collections.java:1308)
at git.snippets.jdk10.CollectionEnhance.main(CollectionEnhance.java:22)
Java 10
增加了兩個新的API
來實現這一點,也就是說,創建完全不能修改的集合。
第一個API
是copyOf
,用來製作集合的不可修改的副本。
static void copyOfTest() {
var list = List.of("a", "b", "c");
var copyList = List.copyOf(list);
list.add("d");
// 由於copyList是副本, 所以copyList不會受到list的影響,列印出[a,b,c]
System.out.println(copyList);
System.out.println(list);
// 由於是不可變集合,所以這裡會報錯
copyList.add("d");
}
這與用Collections.unmodifiableList
包裝一個列表是不同的。 copyOf
是創建副本(源集合改變不會影響副本集合),而Collections.unmodifiableList
是生成視圖(源集合改變會影響視圖)。
第二個API
為Stream
包中的Collectors
類增加的三個新方法。現在你可以使用toUnmodifiableList
、toUnmodifiableSet
和toUnmodifiableMap
在生成一個不可修改的集合。程式碼如下
static void unmodifiedTest() {
List<String> list = List.of("b", "a", "b", "c");
List<String> c1 = list.stream().collect(Collectors.toUnmodifiableList());
System.out.println(c1);
// 會報錯
// c1.add("c");
// System.out.println(c1);
Set<String> c2 = list.stream().collect(Collectors.toUnmodifiableSet());
System.out.println(c2);
// 會報錯
// c2.add("a");
// System.out.println(c2);
// 會報錯
// c2.add("e");
// System.out.println(c2);
}
注意,雖然這些方法的名字可能會讓你想起Collections.unmodifiableList
等,但這些新方法產生的是真正的不可修改的列表,而Collections.unmodifiableList
則返回一個不可修改的視圖。
Unicode 語言標籤擴展
Java SE 10
實現了最新的LDML 規範中指定的更多的擴展。
主要增加了下面幾個擴展方法。
java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider
java.text.DateFormat::get*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of
嘗試一下。
package git.snippets.jdk10;
import java.util.Calendar;
import java.util.Currency;
import java.util.Locale;
/**
* unicode擴展
* @since 10
*/
public class UnicodeTest {
public static void main(String[] args) {
Currency chinaCurrency = Currency.getInstance(Locale.CHINA);
Currency usCurrency = Currency.getInstance(Locale.US);
System.out.println("本地貨幣:" + chinaCurrency);
System.out.println("US.貨幣:" + usCurrency);
String displayName = Locale.getDefault().getDisplayName();
String displayLanguage = Locale.getDefault().getDisplayLanguage();
String displayCountry = Locale.getDefault().getDisplayCountry();
System.out.println("本地名稱:" + displayName);
System.out.println("本地語言:" + displayLanguage);
System.out.println("本地國家:" + displayCountry);
int firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
System.out.println("本地每周第一天:" + firstDayOfWeek);
}
}
輸出結果。
本地貨幣:CNY
US.貨幣:USD
本地名稱:中文 (中國)
本地語言:中文
本地國家:中國
本地每周第一天:1
G1 性能增強
早在 Java 9 時就已經引入了 G1 垃圾收集器,G1 的優點很多。而在 Java 10 中還是做了小小調整,當 G1 的並發收集執行緒不能快速的完成全 GC 時,就會自動切換到並行收集,這可以減少在最壞情況下的 GC 速度。
類數據共享
Java SE 5
引入了類數據共享(CDS
),以改善小型Java
應用程式的啟動時間。
當JVM
第一次啟動時,由引導類載入器載入的任何東西都被序列化並存儲在磁碟上的一個文件中,可以在JVM
的未來啟動中重新載入。這意味著JVM
的多個實例共享類元數據,因此它不必每次都載入它們。
共享數據快取意味著小型應用程式的啟動時間有了很大的改善,因為在這種情況下,核心類的相對大小要大於應用程式本身。
Java SE 10
將此擴展到包括系統類載入器和平台類載入器。為了利用這一點,你只需要添加以下參數
-XX:+UseAppCDS
Java SE 10
還允許你把你自己的應用程式特定的類也存儲到類-數據共享快取中,可能會減少你的啟動時間。
基本上,這是一個三步走的過程。第一步是創建應該被歸檔的類的列表,用適當的標誌啟動你的應用程式,並指出你希望列表被存儲的位置。
java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=myapp.lst \
-cp $CLASSPATH $MAIN_CLASS
然後,用這個清單,你將創建一個CDS
檔案
java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=myapp.lst \
-XX:SharedArchiveFile=myapp.jsa \
-cp $CLASSPATH
最後,運行你的應用程式,使用該存檔
java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
-cp $CLASSPATH $MAIN_CLASS
更多內容參考:JEP 310: Application Class-Data Sharing
新的即時編譯器Graal
即時編譯器(JIT
)是Java
的一部分,它在運行時將Java
位元組碼轉換為機器程式碼。最初的JIT
編譯器是用C++
編寫的,現在被認為相當難以修改。
Java SE 9
引入了一個新的實驗性介面,稱為JVM
編譯器介面或JVMCI
。新介面的設計使得用純Java
重寫JIT
編譯器成為可能。Graal
是由此產生的JIT
編譯器,完全用Java
編寫。
Graal
目前是一個實驗性的JIT
編譯器。在未來的Java
版本之前,只有Linux/x64
機器可以使用它。要啟用Graal
,請在啟動應用程式時在命令行參數中添加這些標誌。
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
更多內容參考:graalvm
Thread-Local Handshakes
在服務性操作中,比如為所有執行緒收集堆棧跟蹤或執行垃圾回收,當JVM
需要暫停一個執行緒時,它需要停止所有執行緒。有時,這些被稱為 “stop-the-world”的暫停。這是由於JVM
想要創建一個全局安全點,一旦JVM
完成工作,所有應用執行緒都可以從這個安全點重新開始。
但在Java SE 10
中,JVM
可以將任意數量的執行緒放入安全點,並且執行緒在執行規定的 “握手”後可以繼續運行。這導致JVM
一次只需暫停一個執行緒,而以前則必須暫停所有執行緒。
更多參考:JEP 312: Thread-Local Handshakes
容器感知
JVM
現在知道它何時在Docker
容器內運行。這意味著應用程式現在擁有關於docker
容器分配給記憶體、CPU
和其他系統資源的準確資訊。
以前,JVM
會查詢主機作業系統來獲得這些資訊,這就造成了一個問題。
例如,假設你想創建一個基於Java
的docker
鏡像,其中運行的JVM
被分配了容器所指定的25%
的可用記憶體。在一個擁有2G
記憶體的盒子上,運行一個配置為0.5G
記憶體的容器,Java SE 9
和更早的版本會錯誤地根據2G
的數字而不是0.5G
來計算Java
進程的堆大小。
但是,現在,在Java SE 10
中,JVM
能夠從容器控制組(cgroups
)中查找這些資訊。
有一些命令行選項可以指定Docker
容器內的JVM
如何分配內部記憶體。例如,為了將記憶體堆設置為容器組的大小,並限制處理器的數量,你可以傳入這些參數
-XX:+UseCGroupMemoryLimitForHeap -XX:ActiveProcessorCount=2
隨著容器成為部署服務的標準方式,這意味著開發者現在有一種基於容器的方式來控制他們的Java
應用如何使用資源。
指定替代的記憶體分配
通過允許用戶指定替代的記憶體設備來分配堆,Java
正朝著更加異構的記憶體系統發展。
一個直接的用例是能夠在非易失性DIMM
(NVDIMM
)模組上分配堆,這在大數據應用中是常用的。
另一個用例是在同一台機器上運行許多JVM
進程。在這種情況下,讓那些需要較低讀取延遲的進程映射到DRAM
上,其餘的進程映射到NVDIMM
上。
可以在你的啟動參數中添加如下標誌
-XX:AllocateHeapAt=<path>
這裡的path
通常是一個記憶體映射的目錄。
更多
參考資料
Java 10 Features (with Examples)
Java 10 Features and Enhancements
JEP 310: Application Class-Data Sharing