在本文中,我將經過示例介紹新的Java SE 10特性——「var」類型。你將學習如何在代碼中正確使用它,以及在什麼狀況下不能使用它。html
Java 10引入了一個閃亮的新功能:局部變量類型推斷。對於局部變量,如今可使用特殊的保留類型名稱「var」代替實際類型,以下所示:java
var name = 「Mohamed Taman」;
提供這個特性是爲了加強Java語言,並將類型推斷擴展到局部變量的聲明上。這樣能夠減小板代碼,同時仍然保留Java的編譯時類型檢查。shell
因爲編譯器須要經過檢查賦值等式右側(RHS)來推斷var的實際類型,所以在某些狀況下,這個特性具備侷限性。我會在稍後提到這個問題。如今,讓咱們來看一些簡單的例子吧。express
在開始演示代碼以前,你須要一個IDE來體驗這些新特性。如今有不少可選擇的IDE,因此你能夠在它們當中選擇你喜歡的可以支持Java SE 10的IDE,好比Apache NetBeans 九、IntelliJ IDEA 2018或最新版本的Eclipse。編程
就我的而言,我更喜歡使用交互式的編程工具,能夠快速學習Java語言語法,瞭解新的Java API及其特性,甚至用來進行復雜代碼的原型設計。這與枯燥的編輯、編譯和執行代碼的繁瑣過程不太同樣:數組
除了IDE以外,如今還可使用從Java SE 9以就隨ava SE JDK一塊兒發佈的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會話,並顯示這個消息:
| Welcome to JShell -- Version 10.0.1 | For an introduction type: /help intro jshell>
如今你已經安裝了JDK 10,如今讓咱們開始玩JShell。咱們直接跳到終端,經過示例來了解var類型。只需在jshell提示符下輸入我接下來要介紹的每一個代碼片斷,我會把結果留給你做爲練習。若是你稍微有瞄過一兩眼在代碼,你會注意到它們看起來好像是錯的,由於當中沒有分號。你能夠試試看,看看能不能運行。
這是var類型的基本用法,在下面的示例中,編譯器能夠將RHS推斷爲String字面量:
var name = "Mohamed Taman" var lastName = str.substring(8) System.out.println("Value: "+lastName +" ,and type is: "+ lastName.getClass().getTypeName())
這裏不須要分號,由於JShell是一個交互式環境。只有當同一行代碼有多個語句或一個類型聲明或方法聲明中有多個語句時才須要分號,你將在後面的示例中看到。
在使用var時,多態仍然有效。在繼承的世界中,var類型的子類型能夠像日常同樣賦值給超類型的var類型,以下所示:
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,以下所示:
password = textField
這是由於JPasswordField是JTextField的子類。
若是出現錯誤的賦值操做會怎樣?不兼容的變量類型不能相互賦值。一旦編譯器推斷出實際類型的var,就不能將錯誤的值賦值給它,以下所示:
var number = 10 number = "InfoQ"
這裏發生了什麼?編譯器將「var number = 10」替換爲「int number = 10」,因此仍然能夠保證安全性。
如今讓咱們來看看var與集合和泛型一塊兒使用時如何進行類型推斷。咱們先從集合開始。在下面的狀況中,編譯器能夠推斷出集合元素的類型是什麼:
var list = List.of(10);
這裏沒有必要進行類型轉換,由於編譯器已經推斷出正確的元素類型爲int。
int i = list.get(0); //等效於: var i = list.get(0);
下面的狀況就不同了,編譯器只會將其做爲對象集合(而不是整數),由於在使用菱形運算符時,Java須要LHS(左側)的類型來推斷RHS的類型:
var list2 = new ArrayList<>(); list2.add(10); list2 int i = list2.get(0) //編譯錯誤 int i = (int) list2.get(0) //須要進行轉換,得到int
對於泛型,最好在RHS使用特定類型(而不是菱形運算符),以下所示:
var list3 = new ArrayList<Integer>(); list3.add(10); System.out.println(list3) int i = list3.get(0)
讓咱們先來看看基於索引的For循環:
for (var x = 1; x <= 5; x++) { var m = x * 2; //等效於: int m = x * 2; System.out.println(m); }
下面是在For Each循環中:
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?讓咱們看看下面的例子:
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 x = 1 > 0 ? 10 : -10 int i = x
如今,若是在三元運算符的RHS中使用不一樣類型的操做數會怎樣?讓咱們來看看:
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的類型是在運行時決定的?絕對不是!讓咱們以舊方式實現一樣的邏輯:
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類型傳給方法。
咱們先聲明一個名爲squareOf的方法,這個方法的參數爲BigDecimal類型,並返回參數的平方,以下所示:
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返回一個整數列表,以下所示:
<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和匿名類。咱們經過實現Runnable接口來使用線程,以下所示:
<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類型的侷限性以及不能將它用在哪些地方。
var message = "running..." //effectively final var runner = new Runnable(){ @Override public void run() { System.out.println(message); }} runner.run()
接下來,你將看一些示例,以便了解var類型功能沒法作到的事情。
jshell提示符將會告訴你代碼出了什麼問題,你能夠利用這些交互式的即時反饋。
第一個也是最簡單的原則就是不容許沒有初始值的變量。
var name;
你將獲得一個編譯錯誤,由於編譯器沒法推斷這個局部變量x的類型。
嘗試運行這行代碼:
var x = 1, y = 3, z = 4
你將獲得一個錯誤消息:複合聲明中不容許使用’var'。
嘗試建立一個名爲testVar的方法,以下所示,將下面的代碼複製並粘貼到JShell中:
void testVar(boolean b) { var x; if (b) { x = 1; } else { x = 2; } System.out.println(x); }
方法不會被建立,而是會拋出編譯錯誤。由於沒有設置初始值,因此不能使用’var'。
不容許進行null賦值,以下所示:
var name = null;
這將拋出異常「variable initializer is 'null'」。由於null不是一個類型。
另外一個例子,沒有Lambda初始化器。這與菱形操做符那個示例同樣,RHS須要依賴LHS的類型推斷。
var runnable = () -> {}
將拋出異常:「lambda expression needs an explicit target-type」。
沒有方法引用初始值,相似於Lambda和菱形運算符示例:
var abs = BigDecimal::abs
將拋出異常:「method reference needs an explicit target-type」。
並不是全部數組初始化都有效,讓咱們看看何時var與[]不起做用:
var numbers[] = new int[]{2, 4, 6}
在此我向你們推薦一個架構學習交流羣。交流學習羣號:821169538 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多。
如下也不起做用:
var numbers = {2, 4, 6}
拋出的錯誤是: 「array initializer needs an explicit target-type」。
就像上一個例子同樣,var和[]不能同時用在LHS一邊:
var numbers[] = {2, 4, 6}
錯誤: 'var' is not allowed as an element type of an array。
只有如下數組初始化是有效的:
var numbers = new int[]{2, 4, 6} var number = numbers[1] number = number + 3
class Clazz { private var name; }
void doAwesomeStuffHere(var salary){}
var getAwesomeStuff(){ return salary; }
try { Files.readAllBytes(Paths.get("c:\temp\temp.txt")); } catch (var e) {}
「var」實際上只是一個語法糖,而且它不會在編譯的字節碼中引入任何新的結構,在運行期間,JVM也沒有爲它們提供任何特殊的指令。