這篇文章主要介紹“Java10局部變量類型舉例分析”,在日常操作中,相信很多人在Java10局部變量類型舉例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java10局部變量類型舉例分析”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)專注于新羅網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供新羅營銷型網(wǎng)站建設(shè),新羅網(wǎng)站制作、新羅網(wǎng)頁設(shè)計(jì)、新羅網(wǎng)站官網(wǎng)定制、重慶小程序開發(fā)服務(wù),打造新羅網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供新羅網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。上下文:陳詞濫調(diào)和代碼可讀性
也許日復(fù)一日,你希望不再需要重復(fù)做一些事情。例如在下面的代碼(使用 Java 9 的集合工廠),左邊的類型也許會感覺到冗余和平淡。
import static java.util.Map.entry;List
這是一個非常簡單的例子,不過它也印證了傳統(tǒng)的 Java 哲學(xué):你需要為所有包含的簡單表達(dá)式定義靜態(tài)類型。再讓我們來看看有一些復(fù)雜的例子。舉例來說,下面的代碼建立了一個從字符串到詞的柱狀圖。它使用 groupingBy 收集器將流聚合進(jìn) Map 。groupingBy 收集器還可以以一個分類函數(shù)為第一個參數(shù)建立映射的鍵和第二個收集器的 (counting()) 鍵計(jì)算關(guān)聯(lián)的數(shù)量。下面就是例子:
String sentence = "A simple Java example that explores what Java10 has to offer";Collector
復(fù)雜表達(dá)式提取到一個變量或方法來提升代碼的可讀性和重用性,這是非常有意義的。在這里例子中,建立柱狀圖的邏輯使用了收集器。不幸地是,來自 groupingBy 的結(jié)果類型幾乎是不可讀的!對于這一點(diǎn)你毫無辦法,你能做的只有觀察。
最重要的一點(diǎn)是當(dāng) Java 中增加新的類庫的時候,他們開發(fā)越來越多的泛型,這就為開發(fā)者引進(jìn)了更多的公式化代碼(boilerplate code),從而帶來了額外的壓力。上面的例子并不是說明了編寫類型就不好。很明顯,強(qiáng)制將為變量和方法簽名定義類型的操作執(zhí)行為一種需要被尊重的協(xié)議,將有益于維護(hù)和理解。然而,為中間表達(dá)式聲明類型也許會顯得無用和冗余。
類型推斷的歷史
我們已經(jīng)在 Java 歷史上多次看到語言設(shè)計(jì)者添加“類型推斷”來幫助我們編寫更簡潔的代碼。類型推斷是一種思想:編譯器可以幫你推出靜態(tài)類型,你不必自己指定它們。
最早從 Java 5 開始就引入了泛型方法,而泛型方法的參數(shù)可以通過上下文推導(dǎo)出來。比如
這段代碼:
List
可以簡化成:
List
然后,在 Java 7 中,可以在表達(dá)式中省略類型參數(shù),只要這些參數(shù)能通過上下文確定。比如:
Map
可以使用尖括號<>運(yùn)算符簡化成:
Map
一般來說,編譯器可以根據(jù)周圍的上下文來推斷類型。在這個示例中,從左側(cè)可以推斷出 HashMap 包含字符串列表。
從 Java 8 開始,像下面這樣的 Lambda 表達(dá)式
Predicate
可以省略類型,寫成
Predicate
局部變量類型推斷
隨著類型越來越多,泛型參數(shù)有可能是另一個泛型,這種情況下類型推導(dǎo)可以增強(qiáng)可讀性。Scala 和 C# 語言允許將局部變量的類型聲明為 var,由編譯器根據(jù)初始化語句來填補(bǔ)合適的類型。比如,前面對 userChannels 的聲明可以寫成這樣:
var userChannels = new HashMap
也可以是根據(jù)方法的返回值(這里返回列表)來推斷:
var channels = lookupUserChannels("Tom");channels.forEach(System.out::println);
這種思想稱為局部變量類型推斷,它已經(jīng)在 Java 10 中引入!
例如下面的代碼:
Path path = Paths.get("src/web.log");try (Stream
在 Java 10 中可以重構(gòu)成這樣:
var path = Paths.get("src/web.log");try (var lines = Files.lines(path)){var warningCount= lines.filter(line -> line.contains("WARNING")).count();System.out.println("Found " + warningCount + " warnings in thelog file");} catch (IOException e) {e.printStackTrace();}
上述代碼中的每個表達(dá)式仍然是靜態(tài)類型(即值的類型):
局部變量 path 的類型是 Path 變量 lines 的類型是 Stream
也就是說,如果給這些變量賦予不同值則會失敗。比如,像下面這樣的二次賦值會造成編譯錯誤:
var warningCount = 5;warningCount = "6";| Error:| incompatible types: java.lang.String cannot be converted to int| warningCount = "6"
然而還有一些關(guān)于類型推斷的小問題;如果類 Car 和 Bike 都是 Vehicle 的子類,然后聲明
var v = new Car();
這里聲明的 v 的類型是 Car 還是 Vehicle?這種情況下很好解釋,因?yàn)槌跏蓟鳎ㄟ@里是 Car)的類型非常明確。如果沒有初始化器,就不能使用 var。稍后像這樣賦值
v = new Bike();
會出錯。換句話說,var 并不能完美地應(yīng)用于多態(tài)代碼。
那應(yīng)該在哪里使用局部變量類型推斷呢?
什么情況下局部類型推斷會失效?你不能在字段和方法簽名中使用它。它只能用于局部變量,比如下面的代碼是不正確的:
public long process(var list) { }
不能在不明確初始化變量的情況下使用 var 聲明局部變量。也就是說,不能使用 var 語法聲明一個沒有賦值的變量。下面這段代碼
var x;
這會產(chǎn)生編譯錯誤:
| Error:| cannot infer type for local variable x| (cannot use 'var' on variable without initializer)| var x;| ^----^
也不能把 var 聲明的變量初始化為 null。實(shí)事上,在后期初始化之前它究竟是什么類型,這并不清楚。
| Error:| cannot infer type for local variable x| (variable initializer is 'null')| var x = null;| ^-----------^
不能在 Lambda 表達(dá)式中使用 var,因?yàn)樗枰鞔_的目標(biāo)類型。下面的賦值就是錯的:
var x = () -> {}| Error:| cannot infer type for local variable x| (lambda expression needs an explicit target-type)| var x = () -> {};| ^---------------^
但是,下面的賦值卻是有效的,原因是等式右邊確實(shí)有一個明確的初始化。
var list = new ArrayList<>();
這個列表的靜態(tài)類型是什么?變量的類型被推導(dǎo)為 ArrayList
對無法表示的類型(Non-Denotable Types)進(jìn)行推斷
Java 中存在大量無法表示的類型——這些類型存在于程序中,但是卻不能準(zhǔn)確地寫出其名稱。比如匿名類就是典型的無法表示的類型,你可以在匿名類中添加字段和方法,但你沒辦法在 Java 代碼中寫出匿名類的名稱。尖括號運(yùn)算符不能用于匿名類,而var 受到的限制會稍微少一些,它可以支持一些無法表示的類型,詳細(xì)點(diǎn)說就是匿名類和交叉類型。
var 關(guān)鍵字也能讓我們更有效地使用匿名類,它可以引用那些不可描述的類型。一般來說是可以在匿名類中添加字段的,但是你不能在別的地方引用這些字段,因?yàn)樗枰兞吭谫x值時指定類型的名稱。比如下面這段代碼就不能通過編譯,因?yàn)?productInfo 的類型是 Object,你不能通過 Object 類型來訪問 name 和 total 字段。
Object productInfo = new Object() {String name = "Apple";int total = 30;};System.out.println("name = " + productInfo.name + ", total = " +productInfo.total);
使用 var 可以打破這個限制。把一個匿名類對象賦值給以 var 聲明的局部變量時,它會推斷出匿名類的類型,而不是把它當(dāng)作其父類類型。因此,匿名類上聲明的字段就可以引用到。
var productInfo = new Object() {String name = "Apple";int total = 30;};System.out.println("name = " + productInfo.name + ", total = " +productInfo.total);
乍一看這只是語言中比較有趣的東西,并不會有太大用處。但在某些情況下它確實(shí)有用。比如你想返回一些值作為中間結(jié)果的時候。一般來說,你會為此創(chuàng)建并維護(hù)一個新的類,但只會在一個方法中使用它。在 Collectors.averagingDouble() 的實(shí)現(xiàn)中就因?yàn)檫@個原因,使用了一個 double 類型的小數(shù)組。
有了 var 之后我們就有了更好的處理辦法 - 用匿名類來保存中間值?,F(xiàn)在來思考一個例子,有一些產(chǎn)品,每個都有名稱、庫存和貨幣價值或價值。我們要計(jì)算計(jì)算每一項(xiàng)的總價(數(shù)量*價值)。這些是我們要將每個 Product 映射到其總價所需要的信息,但是為了讓信息更有意義,還需要加入產(chǎn)品的名稱。下面的示例描述了在 Java 10 中如何使用 var 來實(shí)現(xiàn)這一功能:
var products = List.of(new Product(10, 3, "Apple"),new Product(5, 2, "Banana"),new Product(17, 5, "Pear"));var productInfos = products.stream().map(product -> new Object() {String name = product.getName();int total = product.getStock() * product.getValue();}).collect(toList());productInfos.forEach(prod ->System.out.println("name = " + prod.name + ", total = " +prod.total));This outputs:name = Apple, total = 30name = Banana, total = 10name = Pear, total = 85
并非所有無法表示的類型都可以用 var - 它只支持匿名類和交叉類型。由通配符匹配的類型就不能被推斷,這會避免與通配符相關(guān)的錯誤被報(bào)告給 Java 程序員。支持無法表示的類型的目的是在推斷類型中盡量保留更多信息,讓人們可以利用局部變量并更好地重構(gòu)代碼。這一特性的初衷并不是要人們像上面的示例中那樣編寫代碼,而是為了使用 var 簡化處理無法表示類型相關(guān)的一些問題。以后是否會使用 var 來處理無法表示的類型的一些細(xì)節(jié)問題,尚不可知。
類型推斷建議
類型推斷確實(shí)有助于快速編寫 Java 代碼,但是可讀性如何呢?開發(fā)者大約會花 10 倍于寫代碼的時候來閱讀代碼,因此應(yīng)該讓代碼更易讀而不是更易寫。var 對此帶來的改善程度總是主觀評價的,不可避免地會有人喜歡它,也會有人討厭它。你應(yīng)該關(guān)注的是如何幫助團(tuán)隊(duì)成員閱讀你的代碼,所以如果他們喜歡閱讀使用 var 的代碼,那就用,不然就不用。
有時候,顯示類型也會降低可讀性。比如,在循環(huán)遍歷 Map 的 entryset 時,你需要找到 Map.Entry 對象的類型參數(shù)。這里有一個遍歷 Map 的示例,這個 Map 將國家名稱映射到其中的城市名稱列表。
Map
然后用 var 來重寫這段代碼,減少重復(fù)和繁瑣的東西:
var countryToCity = new HashMap
這里不僅帶來了可讀性方面的優(yōu)勢,在改進(jìn)和維護(hù)代碼方面也帶來了優(yōu)勢。如果我們在顯式類型的代碼中將城市從 String 表示的名稱改為 City 類,以保留更多城市信息,那就需要重寫所有依賴于特定類型的代碼,比如:
Map
但使用了 var 關(guān)鍵字和類型推導(dǎo),我們就只需要修改第一行代碼就好:
var countryToCity = new HashMap
這說明了一個使用 var 變量的重要原則:不要為了易于編碼而優(yōu)化,也不要為了易讀而優(yōu)化,而要了易維護(hù)性而優(yōu)化。同時要考慮部分代碼可能以后會修改而要折衷考慮代碼的可讀性。當(dāng)然如果說添加類型推斷對代碼只會有好處略顯武斷,有時明確的類型有助于代碼可讀性。特別是當(dāng)某些生成的表達(dá)式類型不是很直觀時,我將選擇顯式而不是隱式類型,比如從下邊的代碼中我并不能看出 getCitiest() 方法會返回什么對象:
Map
既然要同時考慮到可讀性和 var ,那么如何折衷就成了一個新問題,一個建議是:關(guān)注變量名,這很重要!因?yàn)?var 失去代碼的易讀性,看到這樣的代碼你根本不知道代碼的意圖是什么,這就使得起好一個變量名更加重要。理論上這是JAVA程序員應(yīng)努力的方面之一,實(shí)際上許多 Java 代碼可讀性的問題根本不在語言的特性本身,而存在于一些變量的命名不太恰當(dāng)上。
IDE 中的類型推斷
許多 IDE 都有提取局部變量的功能,它們可以正確地推斷出變量的類型,并為你寫出來。這一特性與 Java 10 的 var 有一些重復(fù)。IDE 的這個特性和 var 一樣都可以消除顯式書寫類型的必要性,但是它們在其它方面有一些不同。
局部提取功能會在代碼中生成完整的、類型明確的局部變量。而 var 則是消除了在代碼寫顯式書寫類型的必要。所以雖然他們在簡化書寫代碼方面有著類似的作用,但 var 對代碼可讀性的影響是局部提取功能所不具備的。就像我們前面提到,它多數(shù)時候會提高可讀性,但有時候會可能會降低可讀性。
與其它編程語言比較
Java 并不是首先實(shí)現(xiàn)變量類型推斷的語言。類型推斷在近幾十年來被廣泛應(yīng)用于其它語言中。實(shí)際上,Java 10 中通過 var 帶來的類型推斷非常有限,形式上也相對拘束。這是一種簡單的實(shí)現(xiàn),可以將與 var 聲明相關(guān)的編譯錯誤限制在一條語句當(dāng)中,因?yàn)?var 推斷算法只需要計(jì)算賦值給變量的表達(dá)式的類型。此外,用在大多數(shù)語言中的 Hindley-Milner 類型推斷算法在最壞的情況下會花費(fèi)指數(shù)級時間,這會降低 javac 的速度。
到此,關(guān)于“Java10局部變量類型舉例分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!