Java 10 區域性變數型別推斷
在本文中,我們將深入研究Java 10中引入的區域性變數型別推斷的新特性。我們將討論使用區域性變數型別推斷的範圍和限制。
此功能是作為JEP:286(JDK增強提案)的一部分提出的。該提案通過支援對區域性變數宣告和初始化的型別推斷以增強Java語言。
有關Java 10發行版的完整概述,請參閱Java 10新特性。
Java 10:區域性變數型別推斷
使用Java 10,您可以使用var
區域性變數而不是型別名稱(Manifest Type)。這是通過稱為區域性變數型別推斷的新特徵完成的。
但首先,什麼是型別推斷?
型別推斷是指Java編譯器具有檢視每個方法呼叫和相應宣告的能力,以確定呼叫所適用的型別引數。型別推斷不是Java程式設計特有的。
對於使用初始化程式的區域性變數宣告,我們現在可以使用保留型別名稱“var”而不是清單型別。我們來看幾個例子。
var list = new ArrayList<String>(); // infers ArrayList<String>
var stream = list.stream(); // infers Stream<String>
清單型別:宣告的每個變數的型別的顯式標識稱為清單型別。例如,如果變數“actors”將儲存Actor的List,那麼它的型別List 是清單型別,並且必須在Java 10之前宣告它(如下所述):
List<Actor> actors = List.of(new Actor()); // Pre Java 10
var actors = List.of(new Actor()); // Java 10 onwards
區域性變數型別推斷如何工作?
解析var語句時,編譯器檢視宣告語句的右側,即初始化器,並從右側(RHS)表示式推斷出型別。
好吧,這是否意味著現在Java是一種動態型別的語言?並不是,它仍然是一種靜態型別的語言。我們來看一個讀取檔案的程式碼片段。
private static void readFile() throws IOException {
var fileName = "Sample.txt";
var line = "";
var fileReader = new FileReader(fileName);
var bufferedReader = new BufferedReader(fileReader);
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
}
現在,讓我們看一下從IntelliJ IDEA反編譯器中獲取的反編譯程式碼。
private static void readFile() throws IOException {
String fileName = "Sample.txt";
String line = "";
FileReader fileReader = new FileReader(fileName);
BufferedReader bufferedReader = new BufferedReader(fileReader);
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
}
這裡編譯器正確地從右側表示式推斷出變數的型別,並將其新增到位元組碼中。
var是保留型別名稱
var不是關鍵字,它是保留型別名稱。這是什麼意思呢?
- 我們可以建立一個名為“var”的變數。
var var = 5; // syntactically correct
// var is the name of the variable
- 允許使用“var”作為方法名稱。
public static void var() { // syntactically correct
}
- 允許使用“var”作為包名。
package var; // syntactically correct
- “var”不能用作類或介面的名稱。
class var{ } // Compile Error
LocalTypeInference.java:45: error: 'var' not allowed here
class var{
^
as of release 10, 'var' is a restricted local variable type and cannot be used for type declarations
1 error
interface var{ } // Compile Error
區域性變數型別推斷使用場景
區域性型別推斷只能在以下場景中使用:
- 僅限於具有初始化程式的區域性變數
- 增強的for迴圈的索引
- for迴圈中聲名的區域性變數
讓我們來看看這些場景的示例:
var numbers = List.of(1, 2, 3, 4, 5); // inferred value ArrayList<String>
// Index of Enhanced For Loop
for (var number : numbers) {
System.out.println(number);
}
// Local variable declared in a loop
for (var i = 0; i < numbers.size(); i++) {
System.out.println(numbers.get(i));
}
區域性變數型別推理限制
使用var有一定的侷限性,讓我們來看看其中的一部分。
- 沒有初始化,不能對變數使用’var’
如果沒有initailizer,那麼編譯器將無法推斷出型別。
var x;
LocalTypeInference.java:37: error: cannot infer type for local variable x
var x;
^
(cannot use 'var' on variable without initializer)
1 error
- 不能用於多變數定義
var x = 5, y = 10;
LocalTypeInference.java:41: error: 'var' is not allowed in a compound declaration
var x = 5, y = 10;
^
1 error
- Null不能用作var的初始化器
Null不是型別,因此編譯器無法推斷RHS表示式的型別。
var author = null; // Null cannot be inferred to a type
LocalTypeInference.java:47: error: cannot infer type for local variable author
var author = null;
^
(variable initializer is 'null')
1 error
- 不能有額外的陣列維度括號
var actorArr[] = new Actor[10];
LocalTypeInference.java:52: error: 'var' is not allowed as an element type of an array
var actorArr[] = new Actor[10];
^
1 error
- 具有lambda,方法引用和陣列初始值設定項的多語句表示式將觸發錯誤
對於Lambda表示式,方法引用和陣列初始化器的型別推斷,編譯器依賴於左側表示式或傳遞表示式的方法的引數定義,而var使用RHS,這將形成迴圈推斷,因此編譯器生成編譯時錯誤。
var min = (a, b) -> a < b ? a : b;
LocalTypeInference.java:59: error: cannot infer type for local variable min
var min = (a, b) -> a < b ? a : b;
^
(lambda expression needs an explicit target-type)
1 error
var minimum = Math::min;
LocalTypeInference.java:65: error: cannot infer type for local variable minimum
var minimum = Math::min;
^
(method reference needs an explicit target-type)
1 error
var nums = {1,2,3,4,5};
LocalTypeInference.java:71: error: cannot infer type for local variable nums
var nums = {1,2,3,4,5};
^
(array initializer needs an explicit target-type)
1 error
具有區域性變數型別推斷的泛型
Java具有泛型的型別推斷,除此之外,它還必須為任何泛型語句執行型別擦除。在使用泛型的區域性型別引用時,應該理解一些邊界情況。
型別擦除:為了實現泛型,Java編譯器會使用型別擦除。如果型別引數是無界的,則用繫結的泛化型別或Object替換泛型型別中的所有型別引數。
讓我們通過泛型來討論var的一些用例:
var map1 = new HashMap(); // Inferred as HashMap
var map2 = new HashMap<>(); // Inferred as HashMap<Object, Object>
map1 - 編譯器將該map推斷為HashMap,不帶任何泛型型別。
map2 - 菱形運算子依賴於LHS進行型別推斷,這裡編譯器無法推斷LHS,因此它推斷map2具有可以表示HashMap的上限或超型別。這導致map2被推斷為HashMap。
匿名類型別
無法命名匿名類型別,但它們很容易理解——它們只是類。允許變數具有匿名類型別使宣告區域性類的單例例項十分簡單。我們來看一個例子:
var runnable = new Runnable() {
@Override
public void run() {
var numbers = List.of(5, 4, 3, 2, 1);
for (var number : numbers) {
System.out.println(number);
}
}
};
runThread(runnable);
不可表達的型別
無法推斷到特定型別的表示式稱為Non Nototable Type。對於捕獲變數型別,交集型別或匿名類型別,可以稱為這種型別。讓我們看看一個非可表達型別是如何用於區域性變數型別推斷:
var map3 = new HashMap<>() { // anonymous class
int someVar;
};
這裡,當菱形運算子與匿名類型別一起使用時,編譯器無法將RHS表示式推斷為任何特定型別。這導致形成不可表示的型別。
首先,編譯器將通過使用HashMap <>的超型別來獲得可表示的型別,即HashMap
// Special Case Non Denotable Type
var person = new Object() {
class Name {
String firstName;
String lastName;
public Name(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
Name name;
Actor actor;
public String displayName() {
return name.getFirstName() + " " + name.lastName;
}
};
person.name = person.new Name("Rakesh", "Kumar");
System.out.println(person.displayName());
關於var的一些有趣故事
有一個可供選擇的區域性型別推斷的關鍵字列表的調查。以下是向社群使用者提供的語法選項列表:
- var x = expr only(如C#)
- var,及不可變區域性的val (如Scala,Kotlin)
- var,及不可變區域性的let(如Swift)
- auto x = expr(如C ++)
- const x = expr(已經是保留字)
- final x = expr(已經是保留字)
- let x = expr
- def x = expr(如Groovy)
- x:= expr(如Go)
調查結果:
調查選擇的百分比:
使用次優的選擇(var)的理由
- 雖然var是次優的選擇,人們對它的感覺很好,幾乎沒有人討厭它。而其他選擇則不是這種情況。
- C#體驗。C#社群發現該關鍵字對於類似Java的語言來說是合理的。
- 一些讀者發現var / val是如此相似,以至於他們可以忽略差異,並且對不可變和可變變數使用不同的關鍵字會很煩人。
- 大多數區域性變數實際上是final的,並且用另一個形式來懲罰不變性並不是JEP的意圖。
區域性變數型別推斷的好處
- 它改善了開發人員的體驗
- 它減少了程式碼形式
- 它減少了樣板程式碼
- 提高程式碼清晰度
原文地址:Java 10: Local Variable Type Inference written by Rakesh Kumar
完整程式碼:Github