關鍵要點
- java 10引入了一個閃亮的新功能:局部變量類型推斷。對于局部變量,現在可以使用特殊的保留類型名稱“var”代替實際類型。
- 提供這個特性是為了增強java語言,并將類型推斷擴展到局部變量的聲明上。這樣可以減少板代碼,同時仍然保留java的編譯時類型檢查。
- 由于編譯器需要通過檢查賦值等式右側(rhs)來推斷var的實際類型,因此在某些情況下,這個特性具有局限性,例如在初始化array和stream的時候。
- 如何使用新的“var”來減少樣板代碼。
在本文中,我將通過示例介紹新的java se 10特性——“var”類型。你將學習如何在代碼中正確使用它,以及在什么情況下不能使用它。
介紹
java 10引入了一個閃亮的新功能:局部變量類型推斷。對于局部變量,現在可以使用特殊的保留類型名稱“var”代替實際類型,如下所示:
1
|
var name = “mohamed taman”; |
提供這個特性是為了增強java語言,并將類型推斷擴展到局部變量的聲明上。這樣可以減少板代碼,同時仍然保留java的編譯時類型檢查。
由于編譯器需要通過檢查賦值等式右側(rhs)來推斷var的實際類型,因此在某些情況下,這個特性具有局限性。我會在稍后提到這個問題?,F在,讓我們來看一些簡單的例子吧。
在開始演示代碼之前,你需要一個ide來體驗這些新特性?,F在有很多可選擇的ide,所以你可以在它們當中選擇你喜歡的能夠支持java se 10的ide,比如apache netbeans 9、intellij idea 2018或最新版本的eclipse。
就個人而言,我更喜歡使用交互式的編程工具,可以快速學習java語言語法,了解新的java api及其特性,甚至用來進行復雜代碼的原型設計。這與枯燥的編輯、編譯和執行代碼的繁瑣過程不太一樣:
- 寫一個完整的程序;
- 編譯并修復錯誤;
- 運行程序;
- 弄清楚它有什么問題;
- 修改;
- 重復這個過程。
除了ide之外,現在還可以使用從java se 9以就隨ava se jdk一起發布的jshell。
什么是jshell
現在,java有了自己的repl(read-evaluate-print-loop)實現jshell(java shell),作為交互式的編程環境。那么,它有什么神奇的地方?jshell提供了一個快速友好的環境,讓你能夠快速探索、發現和試驗java語言特性及其豐富的庫。
在jshell中,你可以一次輸入一個程序元素,并可以立即看到結果,然后根據需要對代碼做出調整。因此,jshell用它的read-evaluate-print循環取代了編輯、編譯和執行的繁瑣過程。在jshell中,你不需要編寫完整的程序,只需要編寫jshell命令和java代碼片段即可。
當你輸入代碼段時,jshell會立即讀取、執行并打印結果,然后準備好執行下一個代碼片段。因此,jshell的即時反饋可以讓你保持注意力,提高你的效率,并加快學習和軟件開發過程。
對jshell的介紹就到此為止(infoq最近對這個工具進行過全面介紹)。為了深入了解jshell的功能,我錄制了一套視頻教程“hands-on java 10 programming with jshell”,可以幫助你掌握jshell,可以從packt或udemy訪問這些教程。
現在,讓我們通過一些簡單的示例(使用jshell)來了解這個新的var類型能做些什么。
必備軟件
為了能用上jshell,我假設你安裝了java se或jdk 10+,并且jdk的bin目錄已經加入到系統路徑中。如果還沒有安裝,可以在這里下載jdk 10+ 最新版本。
啟動jshell會話
- 在windows上,打開命令提示符,輸入jshell并按回車鍵。
- 在linux上,打開一個shell窗口,輸入jshell并按回車鍵。
- 在macos(以前稱為os x)上,打開終端窗口,輸入“jshell”并按回車鍵。
這個命令會啟動一個新的jshell會話,并顯示這個消息:
1
2
3
|
| welcome to jshell -- version 10.0 . 1 | for an introduction type: /help intro jshell> |
使用“var”類型
現在你已經安裝了jdk 10,現在讓我們開始玩jshell。我們直接跳到終端,通過示例來了解var類型。只需在jshell提示符下輸入我接下來要介紹的每個代碼片段,我會把結果留給你作為練習。如果你稍微有瞄過一兩眼在代碼,你會注意到它們看起來好像是錯的,因為當中沒有分號。你可以試試看,看看能不能運行。
簡單的類型推理
這是var類型的基本用法,在下面的示例中,編譯器可以將rhs推斷為string字面量:
1
2
3
|
var name = "mohamed taman" var lastname = str.substring( 8 ) system.out.println( "value: " +lastname + " ,and type is: " + lastname.getclass().gettypename()) |
這里不需要分號,因為jshell是一個交互式環境。只有當同一行代碼有多個語句或一個類型聲明或方法聲明中有多個語句時才需要分號,你將在后面的示例中看到。
var類型和繼承
在使用var時,多態仍然有效。在繼承的世界中,var類型的子類型可以像平常一樣賦值給超類型的var類型,如下所示:
1
2
3
4
5
6
|
import javax.swing.* var password = new jpasswordfield( "password text" ) string.valueof(password.getpassword()) // // 將密碼的字符數組轉換成字符串 var textfield = new jtextfield( "hello text" ) textfield = password textfield.gettext() |
但不能將超類型var賦值給子類型var,如下所示:
1
|
password = textfield |
這是因為jpasswordfield是jtextfield的子類。
var和編譯時安全性
如果出現錯誤的賦值操作會怎樣?不兼容的變量類型不能相互賦值。一旦編譯器推斷出實際類型的var,就不能將錯誤的值賦值給它,如下所示:
1
2
|
var number = 10 number = "infoq" |
這里發生了什么?編譯器將“var number = 10
”替換為“int number = 10”,
所以仍然可以保證安全性。
var與集合和泛型
現在讓我們來看看var與集合和泛型一起使用時如何進行類型推斷。我們先從集合開始。在下面的情況中,編譯器可以推斷出集合元素的類型是什么:
1
|
var list = list.of( 10 ); |
這里沒有必要進行類型轉換,因為編譯器已經推斷出正確的元素類型為int。
1
|
int i = list.get( 0 ); //等效于: var i = list.get(0); |
下面的情況就不一樣了,編譯器只會將其作為對象集合(而不是整數),因為在使用菱形運算符時,java需要lhs(左側)的類型來推斷rhs的類型:
1
2
3
|
var list2 = new arraylist<>(); list2.add( 10 ); list2 int i = list2.get( 0 ) //編譯錯誤 int i = ( int ) list2.get( 0 ) //需要進行轉換,獲得int |
對于泛型,最好在rhs使用特定類型(而不是菱形運算符),如下所示:
1
2
|
var list3 = new arraylist<integer>(); list3.add( 10 ); system.out.println(list3) int i = list3.get( 0 ) |
for循環中的var類型
讓我們先來看看基于索引的for循環:
1
2
3
4
|
for (var x = 1 ; x <= 5 ; x++) { var m = x * 2 ; //等效于: int m = x * 2; system.out.println(m); } |
下面是在for each循環中:
1
2
3
4
5
|
var list = arrays.aslist( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) for (var item : list) { var m = item + 2 ; system.out.println(m); } |
現在我有一個問題,var是否適用于java 8 stream?讓我們看看下面的例子:
1
2
3
|
var list = list.of( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) var stream = list.stream() stream.filter(x -> x % 2 == 0 ).foreach(system.out::println) |
var類型和三元運算符
那么三元運算符呢?
1
2
|
var x = 1 > 0 ? 10 : - 10 int i = x |
現在,如果在三元運算符的rhs中使用不同類型的操作數會怎樣?讓我們來看看:
1
2
|
var x = 1 > 0 ? 10 : "less than zero" ; system.out.println(x.getclass()) //integer var x = 1 < 0 ? 10 : "less than zero" ; system.out.println(x.getclass()) // string |
這兩個例子是否可以說明var的類型是在運行時決定的?絕對不是!讓我們以舊方式實現同樣的邏輯:
1
|
serializable x = 1 < 0 ? 10 : "less than zero" ; system.out.println(x.getclass()) |
serializable是其中兩個操作數最具兼容性和最專的有類型(最不專有的類型是java.lang.object)。
string和integer都實現了serializable。integer從int自動裝箱。換句話說,serializable是兩個操作數的lub(最小上限)。所以,這表明往前數第三個例子中的var類型也是serializable。
讓我們轉到另一個主題:將var類型傳給方法。
var類型與方法
我們先聲明一個名為squareof的方法,這個方法的參數為bigdecimal類型,并返回參數的平方,如下所示:
1
2
3
4
5
6
7
|
bigdecimal squareof(bigdecimal number) { var result= number.multiply(number); return result; } var number = new bigdecimal( "2.5" ) number = squareof(number) |
現在讓我們看看它如何與泛型一起使用。我們聲明一個名為tointgerlist的方法,參數類型為list<t>(泛型類型),并使用streams api返回一個整數列表,如下所示:
1
2
3
4
5
6
7
8
9
|
<t extends number> list<integer> tointgerlist(list<t> numbers) { var integers = numbers.stream() .map(number::intvalue) .collect(collectors.tolist()); return integers; } var numbers = list.of( 1.1 , 2.2 , 3.3 , 4.4 , 5.5 ) var integers = tointgerlist(numbers) |
var類型與匿名類
最后,讓我們看一下var和匿名類。我們通過實現runnable接口來使用線程,如下所示:
1
2
3
4
5
6
7
8
9
|
<t extends number> list<integer> tointgerlist(list<t> numbers) { var integers = numbers.stream() .map(number::intvalue) .collect(collectors.tolist()); return integers; } var numbers = list.of( 1.1 , 2.2 , 3.3 , 4.4 , 5.5 ) var integers = tointgerlist(numbers) |
到目前為止,我已經介紹了java 10的新特性——“var”類型,它減少了樣板編碼,同時保持了java的編譯時類型檢查。我還通過實例說明了可以用它做些什么。接下來,你將了解var類型的局限性以及不能將它用在哪些地方。
1
2
3
4
5
6
7
8
|
var message = "running..." //effectively final var runner = new runnable(){ @override public void run() { system.out.println(message); }} runner.run() |
“var”的局限性
接下來,你將看一些示例,以便了解var類型功能無法做到的事情。
jshell提示符將會告訴你代碼出了什么問題,你可以利用這些交互式的即時反饋。
應該要進行初始化
第一個也是最簡單的原則就是不允許沒有初始值的變量。
var name;
你將得到一個編譯錯誤,因為編譯器無法推斷這個局部變量x的類型。
不允許復合聲明
嘗試運行這行代碼:
1
|
var x = 1 , y = 3 , z = 4 |
你將得到一個錯誤消息:復合聲明中不允許使用'var'。
不支持確定性賦值(definite assignment)
嘗試創建一個名為testvar的方法,如下所示,將下面的代碼復制并粘貼到jshell中:
1
2
3
4
5
6
7
8
9
10
11
12
|
void testvar( boolean b) { var x; if (b) { x = 1 ; } else { x = 2 ; } system.out.println(x); } |
方法不會被創建,而是會拋出編譯錯誤。因為沒有設置初始值,所以不能使用'var'。
null賦值
不允許進行null賦值,如下所示:
1
|
var name = null ; |
這將拋出異常“variable initializer is 'null'”。因為null不是一個類型。
與lambda一起使用
另一個例子,沒有lambda初始化器。這與菱形操作符那個示例一樣,rhs需要依賴lhs的類型推斷。
1
2
3
|
var runnable = () -> { } |
將拋出異常:“lambda expression needs an explicit target-type”。
var和方法引用
沒有方法引用初始值,類似于lambda和菱形運算符示例:
1
|
var abs = bigdecimal::abs |
將拋出異常:“method reference needs an explicit target-type”。
var和數組初始化
并非所有數組初始化都有效,讓我們看看什么時候var與[]不起作用:
1
2
3
4
|
var numbers[] = new int [] { 2 , 4 , 6 } |
以下也不起作用:
1
2
3
4
|
var numbers = { 2 , 4 , 6 } |
拋出的錯誤是: “array initializer needs an explicit target-type”。
就像上一個例子一樣,var和[]不能同時用在lhs一邊:
1
2
3
4
|
var numbers[] = { 2 , 4 , 6 } |
錯誤: 'var' is not allowed as an element type of an array。
只有以下數組初始化是有效的:
1
2
3
4
5
|
var numbers = new int [] { 2 , 4 , 6 } var number = numbers[ 1 ]number = number + 3 |
不允許使用var字段
1
2
3
4
|
class clazz { private var name; } |
不允許使用var方法參數
1
2
3
|
void doawesomestuffhere(var salary) { } |
不能將var作為方法返回類型
1
2
3
4
|
var getawesomestuff() { return salary; } |
catch子句中不能使用var
1
2
3
4
5
6
7
|
try { files.readallbytes(paths.get( "c:\temp\temp.txt" )); } catch (var e) { } |
在編譯時var類型究竟發生了什么?
“var”實際上只是一個語法糖,并且它不會在編譯的字節碼中引入任何新的結構,在運行期間,jvm也沒有為它們提供任何特殊的指令。
結論
在這篇文章中,我介紹了“var”類型是什么以及它如何減少樣板編碼,同時保持java的編譯時類型檢查。
然后,你了解了新的jshell工具,即java的repl實現,它可以幫助你快速學習java語言,并探索新的java api及其功能。你還可以使用jshell對復雜代碼進行原型設計,而不是重復編輯、編譯和執行的傳統繁瑣流程。
最后,你了解了所有var類型的功能和限制,例如什么時候可以和不可以使用var。寫這篇文章很有意思,所以我希望你喜歡它并能給你帶來幫助。
其他資源
- jdk 10 documentation
- hands-on java 10 programming with jshell .
- getting started with clean code java se 9 .
- overview of jdk 10 and jre 10 installation .
- jep 286: local-variable type inference .
- definite assignment
總結
以上所述是小編給大家介紹的詳解java 10 var關鍵字和示例教程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:http://www.infoq.com/cn/articles/java-10-var-type