Java新特性:數據類型可以扔掉了?
在很久很久以前,我們寫程式碼時要慎重的考慮變數的數據類型,比如下面這些:
枚舉:儘管在 JDK 5 中增加了枚舉類型,但是 Class 文件常量池的 CONSTANT_Class_info 類型常量並沒有發生任何語義變化,仍然是代表一個類或介面的符號引用,沒有加入枚舉,也沒有增加過「CONSTANT_Enum_info」之類的「枚舉符號引用」常量。所以使用 enum 關鍵字定義常量,儘管從 Java 語法上看起來與使用 class 關鍵字定義類、使用 interface 關鍵字定義介面是同一層次的,但實際上這是由 Javac 編譯器做出來的假象,從位元組碼的角度來看,枚舉僅僅是一個繼承於 java.lang.Enum、自動生成了 values() 和 valueOf() 方法的普通 Java 類而已,因此枚舉也歸為引用類型了。
然而到了 JDK 10 時,我們就有了新的選擇,JDK 10 中新增了 var
局部變數推斷的功能,使用它我們可以很 happy 的忘記數據類型這件事了,那它是如何使用的呢?接下來我們一起來看。
1、使用對比
接下來我們就使用對比的方式,來體會一下 var
的作用。
場景一:定義字元串
舊寫法:
String str = "Hello, Java.";
新寫法:
var s = "Hello, Java.";
PS:這裡的舊寫法指的是 JDK 10 之前的版本,而新寫法指的是 JDK 10 以後(包含 JDK 10)的版本。
場景二:數值相加
舊寫法:
int num1 = 111;
double num2 = 555.666d;
double num3 = num1 + num2;
System.out.println(num3);
PS:當遇到不同類型相加時(
int
+double
)會發生數據類型向上轉型,因此num3
就會升級為double
類型。
新寫法:
var n1 = 111L;
var n2 = 555.666;
var n3 = n1 + n2;
System.out.println(n3);
場景三:集合
舊寫法:
List<Object> list = new ArrayList<>();
list.add("Hello");
list.add("Java");
新寫法:
var list = new ArrayList<>();
list.add("Hello");
list.add("Java");
場景四:循環
舊寫法:
for (Object item : list) {
System.out.println("item:" + item);
}
for (int i = 0; i < 10; i++) {
// do something...
}
新寫法:
for (var item : list) {
System.out.println("item:" + item);
}
for (var i = 0; i < 10; i++) {
// do something...
}
場景五:配合 Lambda 使用
舊寫法:
List<Object> flist = list.stream().filter(v ->
v.equals("Java")).collect(Collectors.toList());
System.out.println(flist);
新寫法:
var flist = list.stream().filter(v ->
v.equals("Java")).collect(Collectors.toList());
System.out.println(flist);
2、優點分析
通過上面的示例我們可以看出, var
具備兩個明顯的優點:提高了程式碼的可讀性和命名對齊。
① 提高了可讀性
我們在沒有使用 var
之前,如果類型的名稱很長就會出現下面的這種情況:
InternationalCustomerOrderProcessor<AnonymousCustomer, SimpleOrder<Book>> orderProcessor =
createInternationalOrderProcessor(customer, order);
當限定每行不能超過 150 個字元的話,變數名就會被推到下一行顯示,這樣整個程式碼的可讀性就變得很低。但當我們使用了 var
之後,程式碼就變成了這樣:
var orderProcessor = createInternationalOrderProcessor(customer, order);
從上述的程式碼可以看出,當類型越長時,var
(可讀性)的價值就越大。
② 命名對齊
在不使用 var
時,當遇到下面這種情況,程式碼就是這樣的:
// 顯式類型
No no = new No();
AmountIncrease<BigDecimal> more = new BigDecimalAmountIncrease();
HorizontalConnection<LinePosition, LinePosition> jumping =
new HorizontalLinePositionConnection();
Variable variable = new Constant(6);
List<String> names = List.of("Java", "中文社群");
在使用了 var
之後,程式碼是這樣的:
var no = new No();
var more = new BigDecimalAmountIncrease();
var jumping = new HorizontalLinePositionConnection();
var variable = new Constant(6);
var names = List.of("Java", "中文社群");
從上述程式碼可以看出使用了 var
之後,命名對齊了,整個程式碼也變得更優雅了。
3、使用規則 & 反例
var
的實現來自於 JEP 286 (改善提議 286),詳情地址 ://openjdk.java.net/jeps/286
從 JEP 286 的標題「局部變數類型推斷」可以看出,var
只能用於局部變數聲明,也就是說 var
必須滿足以下條件:
- 它只能用於局部變數上;
- 聲明時必須初始化;
- 不能用作方法參數和全局變數(類變數)。
PS:因為
var
的實現必須根據等會右邊的程式碼進行類型推斷,因此它不能被賦值 null 或不被初始化。
反例一:未初始化和賦值 null
反例二:中途類型更改
反例三:全局變數
反例四:作為返回值
4、原理分析
經過前面的使用我們對 var
已經有了初步的認識,但 var
的實現原理是什麼呢?
為了搞清楚它的原理,我們對下面的程式碼進行了編譯(使用命令 javac MainTest.java
):
然後我們再用反編譯工具打開被編譯的類發現:var
竟然被替換成一個個確定的數據類型了,如下圖所示:
由此我們可以得出結論:var
關鍵字的實現和它的名字密切相關, var
只是局部類型推斷,它只會在 Java 編碼期和編譯期有效,當類被編譯為 class 文件時,var
就會變成一個個確定的數據類型(通過推斷得出)。 所以我們可以把 var
通俗的理解為 Java 的語法糖,使用它可以讓我們快速優雅的實現業務程式碼,但 var
在位元組碼層面是不存在的。
總結
本文我們介紹了 var
(局部類型推斷)的使用,它可以用在局部變數、 for
、 Lambda
的變數聲明中,但不能用在全局變數的聲明中,也不能用它作為方法的返回值,並且在聲明時一定要進行初始化(也不能賦值為 null)。使用 var
可以有效的提高程式碼的可讀性和命名對齊,它的實現原理,是在編譯期通過等號右側的程式碼進行類型推斷,然後再將 var
替換成確定的數據類型。