原文鏈接:https://dzone.com/articles/var-work-in-progress
作者:Anghel Leonard
譯者:沈歌
Java局部變量類型推斷(LVTI)运敢,簡(jiǎn)稱var
類型(標(biāo)識(shí)符var
不是一個(gè)關(guān)鍵字闰围,是一個(gè)預(yù)留類型名)逗爹,Java 10中通過JEP 286: Local-Variable Type Inference 添加進(jìn)來涣脚。作為100%編譯特征糊探,它不會(huì)影響字節(jié)碼,運(yùn)行時(shí)或者性能修赞。在編譯時(shí)询件,編譯器會(huì)檢查賦值語句右側(cè)代碼,從而推斷出具體類型楞卡。它查看聲明的右側(cè)霜运,如果這是一個(gè)初始化語句,它會(huì)用那個(gè)類型取代var
蒋腮。另外淘捡,它非常有助于減少冗余代碼和樣板代碼。它還只在旨在編寫代碼時(shí)所涉及的儀式池摧。例如焦除,使用var evenAndOdd=...
代替Map<Boolean, List<Integer>> evenAndOdd...
非常方便。根據(jù)用例险绘,它有一個(gè)代碼可讀性的權(quán)衡踢京,會(huì)在下面第一條中提到誉碴。
此外,這里有26條細(xì)則瓣距,覆蓋了var
類型的用例黔帕,包括它的限制。
1. 爭(zhēng)取起有意義的局部變量名
通常我們?cè)谄鹑肿兞棵臅r(shí)候會(huì)注意這一點(diǎn)蹈丸,但是選擇局部變量名的時(shí)候不太注意成黄。尤其是當(dāng)方法很短,方法名和實(shí)現(xiàn)都不錯(cuò)的時(shí)候逻杖,我們趨向于簡(jiǎn)化我們的變量名奋岁。但是當(dāng)我們使用var
替代顯式類型的時(shí)候,具體的類型是通過編譯器推斷出來的荸百。所以闻伶,對(duì)于人來說閱讀或者理解代碼非常困難。在這一點(diǎn)上var
削弱了代碼可讀性够话。這種事情之所以會(huì)發(fā)生蓝翰,是因?yàn)榇蠖鄶?shù)情況下,我們會(huì)把變量類型當(dāng)成是第一信息女嘲,而把變量名當(dāng)成第二信息畜份。但是使用var
的時(shí)候,恰恰相反欣尼。
示例1
即使到這里爆雹,一些朋友仍然堅(jiān)持局部變量名短點(diǎn)好。我們看一下:
// HAVING
public boolean callDocumentationTask() {
DocumentationTool dtl = ToolProvider.getSystemDocumentationTool();
DocumentationTask dtt = dtl.getTask(...);
return dtt.call();
}
我們換成var
時(shí)愕鼓,避免:
// AVOID
public boolean callDocumentationTask() {
var dtl = ToolProvider.getSystemDocumentationTool();
var dtt = dtl.getTask(...);
return dtt.call();
}
更好:
// PREFER
public boolean callDocumentationTask() {
var documentationTool = ToolProvider.getSystemDocumentationTool();
var documentationTask = documentationTool.getTask(...);
return documentationTask.call();
}
示例2:
避免:
// AVOID
public List<Product> fetchProducts(long userId) {
var u = userRepository.findById(userId);
var p = u.getCart();
return p;
}
更好:
// PREFER
public List<Product> fetchProducts(long userId) {
var user = userRepository.findById(userId);
var productList = user.getCart();
return productList;
}
示例3:
爭(zhēng)取為局部變量起有意義的名字并不意味著要掉入過度命名的坑钙态,避免在短方法中使用單一類型的數(shù)據(jù)流:
// AVOID
var byteArrayOutputStream = new ByteArrayOutputStream();
用如下代碼代替更加清晰:
// PREFER
var outputStream = new ByteArrayOutputStream();
// or
var outputStreamOfFoo = new ByteArrayOutputStream();
另外,你知道嗎拒啰,Java內(nèi)部使用了一個(gè)類名字叫:InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
額驯绎。。谋旦。命名這個(gè)類型的變量是個(gè)挑戰(zhàn)剩失。
2. 使用數(shù)據(jù)類型標(biāo)志來幫助var去推斷出預(yù)期的基本數(shù)據(jù)類型(int, long, float, double)
如果在基本數(shù)據(jù)類型中不使用有效的數(shù)據(jù)類型標(biāo)志,我們可能會(huì)發(fā)現(xiàn)預(yù)期的類型和推測(cè)出的類型不一致册着。這是由于var
的隱式類型轉(zhuǎn)換導(dǎo)致的拴孤。
例如,下面兩行代碼的表現(xiàn)是符合預(yù)期的甲捏,首先演熟,我們聲明一個(gè)boolean
和一個(gè)char
使用顯式類型:
boolean flag = true; // 這是一個(gè)boolean類型
char a = 'a'; // 這是一個(gè)char類型
現(xiàn)在,我們使用var
代替顯式基本類型:
var flag = true; // 被推斷為boolean類型
var a = 'a'; // 被推斷為char類型
到目前為止,一切都很完美芒粹。接下來兄纺,我們看一下相同邏輯下的int
, long
, double
和 float
:
int intNumber = 20; // 這是int類型
long longNumber = 20; // 這是long類型
float floatNumber = 20; // 這是float類型, 20.0
double doubleNumber = 20; // 這是double類型, 20.0
以上代碼是很常見而且清晰的,現(xiàn)在我們使用var
:
避免:
// AVOID
var intNumber = 20; // 推斷為int
var longNumber = 20; // 推斷為int
var floatNumber = 20; // 推斷為int
var doubleNumber = 20; // 推斷為int
四個(gè)變量都被推斷成了int
化漆。為了修正這個(gè)行為估脆,我們需要依賴Java中的數(shù)據(jù)類型標(biāo)志。
更好實(shí)現(xiàn):
// PREFER
var intNumber = 20; // 推斷為int
var longNumber = 20L; // 推斷為long
var floatNumber = 20F; // 推斷為float, 20.0
var doubleNumber = 20D; // 推斷為double, 20.0
但是如果我們使用小數(shù)聲明一個(gè)數(shù)字座云,會(huì)發(fā)生什么呢疙赠?當(dāng)你認(rèn)為你的數(shù)字是一個(gè)float
的時(shí)候,避免這樣做:
// 避免朦拖,如果這是一個(gè)float
var floatNumber = 20.5; // 推斷為double
你應(yīng)該用對(duì)應(yīng)的數(shù)據(jù)類型標(biāo)志來避免這樣的問題:
// 更好, 如果這是一個(gè)float
var floatNumber = 20.5F; // 推斷為float
3. 在某些情況下圃阳,Var和隱式類型轉(zhuǎn)換可以維持可維護(hù)性
在某些情況下,Var和隱式類型轉(zhuǎn)換可以維持可維護(hù)性璧帝。例如捍岳,假設(shè)我們的代碼包含兩個(gè)方法:第一個(gè)方法接收一個(gè)包含不同條目的購物卡,比較市場(chǎng)中不同的價(jià)格裸弦,計(jì)算出最好的價(jià)格祟同,并匯總返回float
類型的總價(jià)。另一個(gè)方法簡(jiǎn)單的把這個(gè)float
價(jià)格從卡中扣除理疙。
首先,我們看一下計(jì)算最好價(jià)格的方法:
public float computeBestPrice(String[] items) {
...
float price = ...;
return price;
}
然后泞坦,我們看一下扣款的方法:
public boolean debitCard(float amount, ...) {
...
}
現(xiàn)在窖贤,我們把這兩個(gè)方法匯總,提供一個(gè)服務(wù)方法贰锁。顧客選擇要買的商品赃梧,計(jì)算最優(yōu)價(jià)格,然后扣款:
// AVOID
public void purchaseCart(long customerId) {
...
float price = computeBestPrice(...);
debitCard(price, ...);
}
一段時(shí)間后豌熄,公司想要去除價(jià)格中的小數(shù)部分作為打折策略授嘀,使用int
代替了float
, 我們需要修改代碼。
public int computeBestPrice(String[] items) {
...
float realprice = ...;
...
int price = (int) realprice;
return price;
}
public boolean debitCard(int amount, ...) {
...
}
問題在于我們使用了顯示類型float
锣险,這樣的更改不能被兼容蹄皱。代碼會(huì)報(bào)編譯時(shí)錯(cuò)誤。但是如果我們預(yù)判到這種情況芯肤,使用var
代替float
, 我們的代碼會(huì)因?yàn)殡[式類型轉(zhuǎn)換而變得沒有兼容性問題巷折。
// PREFER
public void purchaseCart(long customerId) {
...
var price = computeBestPrice(...);
debitCard(price, ...);
}
4. 當(dāng)數(shù)據(jù)類型標(biāo)志解決不了問題的時(shí)候,依賴顯式向下轉(zhuǎn)換或者避免var
一些Java基礎(chǔ)數(shù)據(jù)類型不支持?jǐn)?shù)據(jù)類型標(biāo)志崖咨。例如byte
和short
锻拘。使用顯式基礎(chǔ)數(shù)據(jù)類型時(shí)沒有任何問題。使用var
代替的時(shí)候:
// 這樣更好击蹲,而不是使用var
byte byteNumber = 45; // 這是byte類型
short shortNumber = 4533; // 這是short類型
為什么在這種情況下顯式類型比var
好呢署拟?我們切換到var
.注意示例中都會(huì)被推斷為int
, 而不是我們預(yù)期的類型婉宰。
避免使用以下代碼:
// AVOID
var byteNumber = 45; // 推斷為int
var shortNumber = 4533; // 推斷為int
這里沒有基礎(chǔ)數(shù)據(jù)類型幫助我們,因此我們需要依賴顯示強(qiáng)制類型轉(zhuǎn)換推穷。從個(gè)人角度來講芍阎,我會(huì)避免這么用,因?yàn)闆]啥好處缨恒,但是可以這么用谴咸。
如果你真的想用var
,這么用:
// 如果你真的想用var骗露,這么寫
var byteNumber = (byte) 45; // 推斷為byte
var shortNumber = (short) 4533; // 推斷為short
5. 如果變量名沒有對(duì)人來說足夠的類型信息岭佳,避免使用var
使用var
有助于提供更加簡(jiǎn)練的代碼。例如, 在使用構(gòu)造方法時(shí)(這是使用局部變量的常見示例)萧锉,我們可以簡(jiǎn)單地避免重復(fù)類名的必要性珊随,從而消除冗余。
避免:
// AVOID
MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(...);
更好:
// PREFER
var inputStream = new MemoryCacheImageInputStream(...);
在下面的結(jié)構(gòu)中柿隙,var
也是一個(gè)簡(jiǎn)化代碼而不丟失信息的好方法叶洞。
避免:
// AVOID
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(...);
更好:
// PREFER
var compiler = ToolProvider.getSystemJavaCompiler();
var fileManager = compiler.getStandardFileManager(...);
為什么這樣基于var
的例子我們感覺比較舒服呢?因?yàn)樾枰男畔⒁呀?jīng)在變量名中了禀崖。但是如果使用var
加上變量名衩辟,還是會(huì)丟失信息,那么最好避免使用var
波附。
避免:
// AVOID
public File fetchCartContent() {
return new File(...);
}
// As a human, is hard to infer the "cart" type without
// inspecting the fetchCartContent() method
var cart = fetchCartContent();
使用以下代碼代替:
// PREFER
public File fetchCartContent() {
return new File(...);
}
File cart = fetchCartContent();
思考一個(gè)基于java.nio.channels.Selector
的例子艺晴。這個(gè)類有一個(gè)靜態(tài)方法叫做open()
,返回一個(gè)新的Selector
實(shí)例并且執(zhí)行open動(dòng)作掸屡。但是Selector.open()
很容易被認(rèn)為返回一個(gè)boolean
標(biāo)識(shí)打開當(dāng)前選擇器是否成功封寞,或者返回void
。使用var
導(dǎo)致丟失信息會(huì)引發(fā)這樣的困擾仅财。
6. var類型確保編譯時(shí)安全
var
類型是編譯時(shí)安全的狈究。這意味著如果我們?cè)噲D實(shí)現(xiàn)一個(gè)錯(cuò)的賦值,會(huì)導(dǎo)致編譯時(shí)報(bào)錯(cuò)盏求。例如抖锥,以下代碼編譯不會(huì)通過。
// 編譯通不過
var items = 10;
items = "10 items"; // 不兼容類型: String不能轉(zhuǎn)為int
以下代碼編譯會(huì)通過
var items = 10;
items = 20;
這個(gè)代碼也會(huì)編譯通過:
var items = "10";
items = "10 items" ;
所以风喇,一旦編譯器已經(jīng)推斷出了var
對(duì)應(yīng)的類型宁改,我們只能賦值對(duì)應(yīng)類型的值給它。
7. var 不能被用于將真實(shí)類型的實(shí)例賦值給接口類型變量魂莫。
在Java中还蹲,我們使用“面向接口編程”的技術(shù)。
例如,我們創(chuàng)建一個(gè)ArrayList
的實(shí)例谜喊,如下(綁定代碼到抽象):
List<String> products = new ArrayList<>();
我們避免這樣的事情(綁定代碼到實(shí)現(xiàn)):
ArrayList<String> products = new ArrayList<>();
所以潭兽,通過第一個(gè)例子創(chuàng)建一個(gè)ArrayList
實(shí)例更好,但是我們也需要聲明一個(gè)List
類型的變量斗遏。因?yàn)?code>List是一個(gè)接口山卦,我們可以很容易的切換到List
的其他實(shí)現(xiàn)類,而無需額外的修改诵次。
這就是“面向接口編程”账蓉,但是var
不能這么用。這意味著當(dāng)我們使用var
時(shí)逾一,推斷出的類型是實(shí)現(xiàn)類的類型铸本。例如,下面這行代碼遵堵,推測(cè)出的類型是ArrayList<String>
:
var productList = new ArrayList<String>(); // 推斷為ArrayList<String>
以下幾個(gè)論點(diǎn)支持這一行為:
- 首先箱玷,
var
是局部變量,大多數(shù)情況下陌宿,“面向接口編程”在方法參數(shù)和返回類型的時(shí)候更有用锡足。 - 局部變量的作用域比較小,切換實(shí)現(xiàn)引起的發(fā)現(xiàn)和修復(fù)成本比較低壳坪。
- var將其右側(cè)的代碼視為用于對(duì)端實(shí)際類型的初始化程序舶得,如果將來修改初始化程序,則推斷類型會(huì)改變弥虐,從而導(dǎo)致后續(xù)依賴此變量的代碼產(chǎn)生問題扩灯。
8. 意外推斷類型的可能性
如果不存在推斷類型所需的信息,則與菱形運(yùn)算符組合的var
類型可能導(dǎo)致意外推斷類型霜瘪。
在Java 7之前的Coin項(xiàng)目中,我們寫了這樣的代碼:
//顯式指定泛型類的實(shí)例化參數(shù)類型
List<String> products = new ArrayList<String>();
從Java 7開始惧磺,我們有了菱形運(yùn)算符颖对,它能夠推斷泛型類實(shí)例化參數(shù)類型:
// inferring generic class's instantiation parameter type
List<String> products = new ArrayList<>();
那么,以下代碼推斷出什么類型呢磨隘?
首先應(yīng)該避免這么用:
// AVOID
var productList = new ArrayList<>(); // 推斷為ArrayList<Object>
推斷出的類型是Object的ArrayList缤底。之所以會(huì)這樣是因?yàn)闆]有找到能夠推測(cè)到預(yù)期類型為String的信息,這會(huì)導(dǎo)致返回一個(gè)最廣泛可用類型番捂,Object个唧。
所以為了避免這樣的情形,我們必須提供能夠推斷到預(yù)測(cè)類型的信息设预。這個(gè)可以直接給也可以間接給徙歼。
更好的實(shí)現(xiàn)(直接):
// PREFER
var productList = new ArrayList<String>(); // 推斷為ArrayList<String>
更好的實(shí)現(xiàn)(間接):
var productStack = new ArrayDeque<String>();
var productList = new ArrayList<>(productStack); // 推斷為ArrayList<String>
更好的實(shí)現(xiàn)(間接):
Product p1 = new Product();
Product p2 = new Product();
var listOfProduct = List.of(p1, p2); // 推斷為L(zhǎng)ist<Product>
// 不要這么干
var listofProduct = List.of(); // 推斷為L(zhǎng)ist<Object>
listofProduct.add(p1);
listofProduct.add(p2);
9. 賦值數(shù)組到var不需要中括號(hào)[]
我們都知道Java中如何聲明一個(gè)數(shù)組:
int[] numbers = new int[5];
// 或者,這樣寫不太好
int numbers[] = new int[5];
那么怎么用var呢?左邊不需要使用括號(hào)魄梯。
避免這么寫(編譯不通過):
// 編譯通不過
var[] numbers = new int[5];
// 或者
var numbers[] = new int[5];
應(yīng)該這么用:
// PREFER
var numbers = new int[5]; // 推斷為int數(shù)組
numbers[0] = 2; // 對(duì)
numbers[0] = 2.2; // 錯(cuò)
numbers[0] = "2"; // 錯(cuò)
另外桨螺,這么用也不能編譯,這是因?yàn)橛疫厸]有自己的類型酿秸。
// 顯式類型表現(xiàn)符合預(yù)期
int[] numbers = {1, 2, 3};
// 編譯通不過
var numbers = {1, 2, 3};
var numbers[] = {1, 2, 3};
var[] numbers = {1, 2, 3};
10. var類型不能被用于復(fù)合聲明(一行聲明多個(gè)變量)
如果你是復(fù)合聲明的粉絲灭翔,你一定要知道var
不支持這種聲明。下面的代碼不能編譯:
// 編譯通不過
// error: 'var' 不允許復(fù)合聲明
var hello = "hello", bye = "bye", welcome = "welcome";
用下面的代碼代替:
// PREFER
String hello = "hello", bye = "bye", welcome = "welcome";
或者這么用:
// PREFER
var hello = "hello";
var bye = "bye";
var welcome = "welcome";
11. 局部變量應(yīng)力求最小化其范圍辣苏。var類型強(qiáng)化了這一論點(diǎn)肝箱。
局部變量應(yīng)該保持小作用域,我確定你在var
出現(xiàn)之前就聽過這個(gè)稀蟋,這樣可以增強(qiáng)代碼可讀性煌张,也方便更快的修復(fù)bug。
例如我們定義一個(gè)java棧:
避免:
// AVOID
...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// 50行不用stack的代碼
// George, Tyllen, Martin, Kelly
stack.forEach(...);
...
注意我們調(diào)用forEach
方法糊治,該方法繼承自java.util.Vector
.這個(gè)方法將以Vector的方式遍歷棾。現(xiàn)在我們準(zhǔn)備切換Stack
到ArrayDeque
井辜,切換之后forEach()
方法將變成ArrayDeque
的绎谦,將以stack(LIFO)的方式遍歷stack。
// AVOID
...
var stack = new ArrayDeque<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// 50行不用stack的代碼
// Kelly, Martin, Tyllen, George
stack.forEach(...);
...
這不是我們想要的粥脚,我們很難看出引入了一個(gè)錯(cuò)誤窃肠,因?yàn)榘?code>forEach()部分的代碼不在研發(fā)完成修改的代碼附近。為了快速修復(fù)這個(gè)錯(cuò)誤刷允,并避免上下滾動(dòng)來了解發(fā)生了什么冤留,最好縮小stack變量的作用域范圍。
最好這么寫:
// PREFER
...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...
// George, Tyllen, Martin, Kelly
stack.forEach(...);
...
// 50行不用stack的代碼
現(xiàn)在树灶,當(dāng)開發(fā)人員從Stack
切換到ArrayQueue
的時(shí)候纤怒,他們能夠很快的注意到bug,并修復(fù)它天通。
12. var類型便于三元運(yùn)算符右側(cè)的不同類型的操作數(shù)
我們可以在三元運(yùn)算符的右側(cè)使用不同類型的操作數(shù)泊窘。
使用具體類型的時(shí)候,以下代碼無法編譯:
// 編譯通不過
List code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// or
Set code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
雖然我們可以這么寫:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
這樣也編譯不過:
// 編譯通不過:
int code = intOrString ? 12112 : "12112";
String code = intOrString ? 12112 : "12112";
但是我們可以這么寫:
Serializable code = intOrString ? 12112 : "12112";
Object code = intOrString ? 12112 : "12112";
在這種情況下像寒,使用var
更好:
// PREFER
// inferred as Collection<Integer>
var code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// inferred as Serializable
var code = intOrString ? 12112 : "12112";
千萬不要從這些例子中得出var類型是在運(yùn)行時(shí)做類型推斷的烘豹,它不是!E祷觥携悯!
當(dāng)然,我們使用相同的類型作為操作數(shù)時(shí)var
是支持的筷笨。
// 推斷為float
var code = oneOrTwoDigits ? 1211.2f : 1211.25f;
13. var類型能夠用在循環(huán)體中
我們能非常簡(jiǎn)單的在for循環(huán)中用var
類型取代具體類型憔鬼。這是兩個(gè)例子龟劲。
var替換int:
// 顯式類型
for (int i = 0; i < 5; i++) {
...
}
// 使用 var
for (var i = 0; i < 5; i++) { // i 推斷為 int類型
...
}
var替換Order:
List<Order> orderList = ...;
// 顯式類型
for (Order order : orderList) {
...
}
// 使用 var
for (var order : orderList) { // order 推斷成Order類型
...
}
14. var類型能夠和Java 8中的Stream一起用
將Java10中的var與Java 8中的Stream結(jié)合起來非常簡(jiǎn)單。
你需要使用var取代顯式類型Stream:
例1:
// 顯式類型
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);
// 使用 var
var numbers = Stream.of(1, 2, 3, 4, 5); // 推斷為 Stream<Integer>
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);
例2:
// 顯式類型
Stream<String> paths = Files.lines(Path.of("..."));
List<File> files = paths.map(p -> new File(p)).collect(toList());
// 使用 var
var paths = Files.lines(Path.of("...")); // 推斷為 Stream<String>
var files = paths.map(p -> new File(p)).collect(toList()); // 推斷為 List<File>
15. var類型可用于聲明局部變量逊彭,可用于分解表達(dá)式嵌套/長(zhǎng)鏈
var
類型可用于聲明局部變量咸灿,可用于分解表達(dá)式嵌套/長(zhǎng)鏈.
大的或者嵌套的表達(dá)看起來令人印象深刻,通常它們被認(rèn)為是聰明的代碼侮叮。有時(shí)候我們會(huì)故意這么寫避矢,有時(shí)候我們從一個(gè)小表達(dá)式開始寫,慢慢越來越大囊榜。為了提高代碼可讀性审胸,建議用局部變量來破壞大型/嵌套表達(dá)式。但有時(shí)候卸勺,添加這些局部變量是我們想要避免的體力活砂沛。如下:
避免:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// AVOID
int result = intList.stream()
.collect(Collectors.partitioningBy(i -> i % 2 == 0))
.values()
.stream()
.max(Comparator.comparing(List::size))
.orElse(Collections.emptyList())
.stream()
.mapToInt(Integer::intValue)
.sum();
更好:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// PREFER
Map<Boolean, List<Integer>> evenAndOdd = intList.stream()
.collect(Collectors.partitioningBy(i -> i % 2 == 0));
Optional<List<Integer>> evenOrOdd = evenAndOdd.values()
.stream()
.max(Comparator.comparing(List::size));
int sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
.stream()
.mapToInt(Integer::intValue)
.sum();
第二段代碼可讀性更強(qiáng),更簡(jiǎn)潔曙求,但是第一段代碼也是對(duì)的碍庵。我們的思維會(huì)適應(yīng)這樣的大表達(dá)式并且更喜歡它們而不是局部變量。然而悟狱,使用var類型對(duì)于使用局部變量的方式來說是一個(gè)優(yōu)化静浴,因?yàn)樗?jié)省了獲取顯式類型的時(shí)間。
更好
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
// PREFER
var evenAndOdd = intList.stream()
.collect(Collectors.partitioningBy(i -> i % 2 == 0));
var evenOrOdd = evenAndOdd.values()
.stream()
.max(Comparator.comparing(List::size));
var sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
.stream()
.mapToInt(Integer::intValue)
.sum();
16. var類型不能被用于方法返回類型或者方法參數(shù)類型挤渐。
試著寫下面的兩段代碼苹享,編譯通不過。
使用var作為方法返回類型:
// 編譯通不過
public var countItems(Order order, long timestamp) {
...
}
使用var作為方法參數(shù)類型:
// 編譯通不過
public int countItems(var order, var timestamp) {
...
}
17. var類型的局部變量可以用來傳入到方法參數(shù)浴麻,也可以用來存放方法返回值
var
類型的局部變量可以用來傳入到方法參數(shù)得问,也可以用來存放方法返回值。下面這兩段代碼能夠編譯而且運(yùn)行软免。
public int countItems(Order order, long timestamp) {
...
}
public boolean checkOrder() {
var order = ...; // Order實(shí)例
var timestamp = ...; // long類型的 timestamp
var itemsNr = countItems(order, timestamp); // 推斷為int類型
...
}
它也適用于泛型宫纬。下面的代碼片段也是對(duì)的。
public <A, B> B contains(A container, B tocontain) {
...
}
var order = ...; // Order實(shí)例
var product = ...; // Product實(shí)例
var resultProduct = contains(order, product); // inferred as Product type
18. var類型能和匿名類一起使用膏萧。
避免:
public interface Weighter {
int getWeight(Product product);
}
// AVOID
Weighter weighter = new Weighter() {
@Override
public int getWeight(Product product) {
...
}
};
Product product = ...; // Product實(shí)例
int weight = weighter.getWeight(product);
更好的代碼:
public interface Weighter {
int getWeight(Product product);
}
// PREFER
var weighter = new Weighter() {
@Override
public int getWeight(Product product) {
...
}
};
var product = ...; // Product實(shí)例
var weight = weighter.getWeight(product);
19. var類型可以是Effectively Final
從Java SE 8開始哪怔,局部類可以訪問封閉塊內(nèi)final或者effectively final的參數(shù)。變量初始化后不再改變的參數(shù)為effectively final向抢。
所以,var類型的變量可以是effectively final的胚委。我們可以從以下代碼中看到挟鸠。
避免:
public interface Weighter {
int getWeight(Product product);
}
// AVOID
int ratio = 5; // 這是effectively final
Weighter weighter = new Weighter() {
@Override
public int getWeight(Product product) {
return ratio * ...;
}
};
ratio = 3; // 這行賦值語句會(huì)報(bào)錯(cuò)
更好:
public interface Weighter {
int getWeight(Product product);
}
// PREFER
var ratio = 5; // 這是effectively final
var weighter = new Weighter() {
@Override
public int getWeight(Product product) {
return ratio * ...;
}
};
ratio = 3; // 這行賦值語句會(huì)報(bào)錯(cuò)
20. var類型可以用final修飾
默認(rèn)情況下,var類型的局部變量可以被重新賦值(除非它是effectively final的)亩冬。但是我們可以聲明它為final類型艘希,如下:
避免:
// AVOID
// IT DOESN'T COMPILE
public void discount(int price) {
final int limit = 2000;
final int discount = 5;
if (price > limit) {
discount++; // 這行會(huì)報(bào)錯(cuò)
}
}
更好:
// PREFER
// IT DOESN'T COMPILE
public void discount(int price) {
final var limit = 2000;
final var discount = 5;
if (price > limit) {
discount++; // 這行會(huì)報(bào)錯(cuò)
}
}
21. Lambda表達(dá)式和方法引用需要顯示對(duì)象類型
當(dāng)對(duì)應(yīng)的類型推斷不出來時(shí)不能使用var類型硼身。所以,lambda表達(dá)式和方法引用初始化不被允許覆享。這是var類型限制的一部分佳遂。
下面的代碼無法編譯:
// 編譯不通過
// lambda表達(dá)式需要顯式目標(biāo)類型
var f = x -> x + 1;
// 方法引用需要顯式目標(biāo)類型
var exception = IllegalArgumentException::new;
用以下代碼代替:
// PREFER
Function<Integer, Integer> f = x -> x + 1;
Supplier<IllegalArgumentException> exception = IllegalArgumentException::new;
但是在lambda的內(nèi)容中,Java 11允許我們?nèi)ナ褂胿ar作為lambda參數(shù)撒顿。例如丑罪,以下代碼在Java 11中可以很好的工作(詳見JEP 323(lambda參數(shù)中的局部變量))
// Java 11
(var x, var y) -> x + y
// or
(@Nonnull var x, @Nonnull var y) -> x + y
22. 為var類型賦值為null是不被允許的。
此外凤壁,也不允許缺少初始化程序吩屹。這是var類型的另一個(gè)限制。
以下代碼不會(huì)編譯通過(賦值null):
// 編譯通不過
var message = null; // 類型錯(cuò)誤: 變量初始化為'null'
這個(gè)代碼也不會(huì)編譯通過(缺少初始化):
// IT DOESN'T COMPILE
var message; // 使用var不能不做初始化
...
message = "hello";
更好:
// PREFER
String message = null;
// or
String message;
...
message = "hello";
23. var類型不能作為對(duì)象的域(Field)
var類型可以用來做局部變量拧抖,但是不能用來做對(duì)象的域/全局變量煤搜。
這個(gè)限制會(huì)導(dǎo)致這里的編譯錯(cuò)誤:
// 編譯通不過
public class Product {
private var price; // 'var' 不被允許
private var name; // 'var' 不被允許
...
}
用以下代碼代替:
// PREFER
public class Product {
private int price;
private String name;
...
}
24. var不被允許在catch塊中使用
但是它被允許在try-with-resources中。
catch塊
當(dāng)代碼拋出異常時(shí)唧席,我們必須通過顯式類型catch它味悄,因?yàn)関ar類型不被允許。這個(gè)限制會(huì)導(dǎo)致以下代碼的編譯時(shí)錯(cuò)誤:
// 編譯通不過
try {
TimeUnit.NANOSECONDS.sleep(5000);
} catch (var ex) {
...
}
用這個(gè)取代:
// PREFER
try {
TimeUnit.NANOSECONDS.sleep(5000);
} catch (InterruptedException ex) {
...
}
try-with-resources
另一方面映跟,var類型可以用在try-with-resource中辜纲,例如:
// 顯式類型
try (PrintWriter writer = new PrintWriter(new File("welcome.txt"))) {
writer.println("Welcome message");
}
可以用var重寫:
// 使用 var
try (var writer = new PrintWriter(new File("welcome.txt"))) {
writer.println("Welcome message");
}
25. var類型不能和泛型T一起使用
假定我們有下面的代碼:
public <T extends Number> T add(T t) {
T temp = t;
...
return temp;
}
這種情況下,使用var的運(yùn)行結(jié)果是符合預(yù)期的绞绒,我們可以用var替換T婶希,如下:
public <T extends Number> T add(T t) {
var temp = t;
...
return temp;
}
我們看一下另一個(gè)var能夠成功使用的例子,如下:
public <T extends Number> T add(T t) {
List<T> numbers = new ArrayList<>();
numbers.add((T) Integer.valueOf(3));
numbers.add((T) Double.valueOf(3.9));
numbers.add(t);
numbers.add("5"); // 錯(cuò)誤:類型不兼容蓬衡,string不能轉(zhuǎn)為T
...
}
可以用var取代List<T>, 如下:
public <T extends Number> T add(T t) {
var numbers = new ArrayList<T>();
// DON'T DO THIS, DON'T FORGET THE, T
var numbers = new ArrayList<>();
numbers.add((T) Integer.valueOf(3));
numbers.add((T) Double.valueOf(3.9));
numbers.add(t);
numbers.add("5"); // // 錯(cuò)誤:類型不兼容喻杈,string不能轉(zhuǎn)為T
...
}
26. 使用帶有var類型的通配符(?),協(xié)方差和反對(duì)變量時(shí)要特別注意
使用狰晚?通配符
這么做是安全的:
// 顯式類型
Class<?> clazz = Integer.class;
// 使用var
var clazz = Integer.class;
但是筒饰,不要因?yàn)榇a中有錯(cuò)誤,而var可以讓它們魔法般的消失壁晒,就使用var取代Foo<?>瓷们。看下一個(gè)例子秒咐,不是非常明顯谬晕,但是我想讓它指出核心⌒。考慮一下當(dāng)你編寫這一段代碼的過程攒钳,也許,你嘗試定義一個(gè)String的ArrayList雷滋,并最終定義成了Collection<?>不撑。
// 顯式類型
Collection<?> stuff = new ArrayList<>();
stuff.add("hello"); // 編譯錯(cuò)誤
stuff.add("world"); // 編譯錯(cuò)誤
// 使用var文兢,錯(cuò)誤會(huì)消失,但是我不確定你是你想要的結(jié)果
var stuff = new ArrayList<>();
strings.add("hello"); // 錯(cuò)誤消失
strings.add("world"); // 錯(cuò)誤消失
Java協(xié)變(Foo<? extends T>)和逆變(Foo<? super T>)
我們知道可以這么寫:
// 顯式類型
Class<? extends Number> intNumber = Integer.class;
Class<? super FilterReader> fileReader = Reader.class;
而且如果我們錯(cuò)誤賦值了錯(cuò)誤的類型焕檬,接收到一個(gè)編譯時(shí)錯(cuò)誤姆坚,這就是我們想要的:
// 編譯通不過
// 錯(cuò)誤: Class<Reader> 不能轉(zhuǎn)換到 Class<? extends Number>
Class<? extends Number> intNumber = Reader.class;
// 錯(cuò)誤: Class<Integer> 不能轉(zhuǎn)化到Class<? super FilterReader>
Class<? super FilterReader> fileReader = Integer.class;
但是如果我們使用var:
// using var
var intNumber = Integer.class;
var fileReader = Reader.class;
然后我們可以為這些變量賦值任何類,因此我們的邊界/約束消失了实愚,這并不是我們想要的兼呵。
// 編譯通過
var intNumber = Reader.class;
var fileReader = Integer.class;
結(jié)論
var類型非常酷爆侣,而且將來會(huì)繼續(xù)升級(jí)萍程。留心JEP 323 和 JEP 301了解更多。祝您愉快兔仰。
任何人想要轉(zhuǎn)載我的文章茫负,無需和我聯(lián)系,請(qǐng)轉(zhuǎn)載后把鏈接私信貼給我乎赴,謝謝忍法!