Java 10 將對 Lambda 表達式進行升級
新的 JEP 對增強 Lambda 提出了修改建議,包括消除歧意、用下劃線表示未使用的參數、影子外部變量 等。雖然這些改變會讓 Java 的 Lambda 更接近于其它語言的 Lambda,不過 最初的討論 只是想支持混合水平。這個 JEP 補充了一系列的提議來改善 Java 語言,包括 局部變量的類型推斷 和 增強的枚舉 ,這些內容都有可能包含在 Java 10 中。
盡管與 Lambda 相關的改變只有 3 個,但它們之間并沒什么聯系,它們中哪個更受歡迎完全取決于反饋。因此,我們將在本文中分別進行說明。
更好地消除歧義
Lambda 從 Java 8 開始加入 Java 語言,這需要修改類型推導來支持它們。但是之前的改變并沒有達到它們的需求,而部分原因是那些改變可能會讓新接觸 Lambda 的開發者困惑。不過現在情況正在發生變化,在上下文提供了足夠多信息的情況下,編譯器仍然不能推導 Lambda 的類型會讓開發者們沮喪。下面的例子說明了這個時候的 Lambda 類型推導:
// 情況 1: Lambda 被推導為 Predicate<String> 類型
// 第一個函數重載被調用。
private void m(Predicate<String> ps) { /* ... */ }
private void m(Function<String, String> fss) { /* ... */ }
private void callingM() {
m((String s) -> s.isEmpty());
}
// 情況 2: 沒有足夠的信息來推導 Lambda 的類型,
// 不過 m2 沒有重載。
// 方法參數的類型會用來推斷 Lambda,
// 所以 s 是 String, s.length() 返回 Integer
private void m2(Function<String, Integer> fsi) { /* ... */ }
private void callingM2() {
m2(s -> s.length());
}
// 情況 3: 沒有足夠的信息來推導 Lambda 的類型。
// m3 有重載,不同的重載有不同的參數數量,
// 只有第一個重載匹配1個參數。
// 方法參數的類型會用于推斷 Lambda,
// 所以 s 是 String, s.length() 返回 Integer
private void m3(Function<String, Integer> fsi) { /* ... */ }
private void m3(Function<String, Integer> fsi, String s) { /* ... */ }
private void callingM3() {
m3(s -> s.length());
}
// 情況 4: 沒有足夠的信息來推導 Lambda的類型
// m4 的多個重載參數數量相同,
// 不清楚該調用哪一個,錯誤
private void m4(Predicate<String> ps) { /* ... */ }
private void m4(Function<String, String> fss) { /* ... */ }
private void callingM4() {
m4(s -> s.isEmpty());
}
在最后一種情況下,有足夠的信息可以推導出 m4 的第一個重載可用,然而當前的編譯器不會使用這些信息。在新的提議下,編譯器會通過以下步驟消除歧義:
-
兩種可能的情況都需要 Lambda 的參數是 String,所以 s 可以認定為 String 類型
-
現在知道 s 是 String 了,那就會知道 String.isEmpty() 返回 boolean
-
既然 Labmda 返回 boolean,m4 的第二個重載就不能匹配,排除掉它
-
剩下唯一可選的是 m4 的第一個重載,它與根據 Lambda 推導的類型匹配,所以使用它
類似的論證可以應用于方法引用。
用下劃線表示不用的參數
在某些情況下,預期 Lambda 會有多個參數,雖然代碼塊中不會完全用到,卻要開發者為這些不用的參數命名。這個改變允許使用下劃線來表示不使用的參數。
Function<String, Integer> noneByDefault = notUsed -> 0; // 當前
Function<String, Integer> noneByDefault = _ -> 0; // 提議
這個特性已經存在于其它一些語言,比如 Scala、Ruby 或 Prolog。不過到 Java 7,這都并不容易實現, 因為下劃線是一個合法的標識符,所以代碼中可能會用到。為了在不引起大量重寫代碼的前提下引入這個改變, 就不能操之過急:
-
Java 8:如果下劃線用作標識符,會產生一個警告,告訴開發者避免使用它;在 Lambda 中不允許使用下劃線(這不會引起向后兼容的問題,因為 Lambda 是 Java 8 引入的)。
-
Java 9:前面提到的警告已經轉變為錯誤,這確保 Java 代碼中不使用下劃線作為標識符。
-
Java 10 (及以后):下劃線再次可用作標識符,但它只能作為 Lambda 表達式的參數使用。
從一開始就 并非所有人都一致 支持這個改變;有些用戶喜歡新提議帶來的簡潔語法,而另外一些人則喜歡使用明確的名稱。進一步討論也許能達成共識。
影子參數
[譯者注:shadow variable,通常譯為隱藏變量。它是指在某個用域中定義的變量名與它直接的外部作用域的某個變量重名,那么在當前作用域,這個變量就隱藏外部作用域的同名變量。shadow parameter 意義近似。]
也許這是新提議中最有爭議的一個功能。目前 Lambda 的參數不能隱藏外部變量,這就意味著在當前作用域內必須選擇與其它可訪問變量不同的名稱;這與其它封裝的作用域工作原理類似,比如 while 循環或 if 語句:
String s = "hello";
if(finished) {
String s = "bye"; // 錯誤,s 已經定義了
}
Predicate<String> ps = s -> s.isEmpty(); // 錯誤,s 已經定義了
如果這個提議被接受,Lambda 的參數就可以隱藏外部已存在的標識符并再次使用它。它的好處是某些情況下不再需要一個意義不太明確的 Labmda 參數名(對上面的例子的一個典型的修正是 s2 -> s2.isEmpty())。不過它也可能帶來像 Roy Van Rijn,國際有名的演說家,提到的潛在錯誤, 它提到 :
Map<String, Integer> map = /* ... */
String key = "theInitialKey";
map.computeIfAbsent(key, _ -> {
String key = "theShadowKey"; // 影子變量
return key.length();
});
目前上面的代碼還不是正確的代碼,但在新提議下它就是正確的。如果注釋“影子變量”那一行被刪掉,代碼仍然可以編譯和運行,但它會做完全不同的事情。
仍然需要通過大量的討論來評估是否將上面提到的東西引入 Java,以及以什么樣的形式引入。不管怎么說,在 Java 8 中引入 Lambda 很明顯是第一步,接下來還有一大批對 Java 的改進。
來自:https://www.oschina.net/translate/java-10-could-bring-upgraded-lambdas