1. 程式人生 > >Java 10 區域性變數型別推斷

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)

調查結果:
java區域性變數型別推斷
調查選擇的百分比:
java var變數

使用次優的選擇(var)的理由

  • 雖然var是次優的選擇,人們對它的感覺很好,幾乎沒有人討厭它。而其他選擇則不是這種情況。
  • C#體驗。C#社群發現該關鍵字對於類似Java的語言來說是合理的。
  • 一些讀者發現var / val是如此相似,以至於他們可以忽略差異,並且對不可變和可變變數使用不同的關鍵字會很煩人。
  • 大多數區域性變數實際上是final的,並且用另一個形式來懲罰不變性並不是JEP的意圖。

區域性變數型別推斷的好處

  • 它改善了開發人員的體驗
  • 它減少了程式碼形式
  • 它減少了樣板程式碼
  • 提高程式碼清晰度

原文地址:Java 10: Local Variable Type Inference written by Rakesh Kumar
完整程式碼:Github