詳談Java8新特性泛型的類(lèi)型推導(dǎo)
1. 泛型究竟是什么?
在討論類(lèi)型推導(dǎo)(type inference)之前,必須回顧一下什么是泛型(Generic).泛型是Java SE 1.5的新特性,泛型的本質(zhì)是參數(shù)化類(lèi)型,也就是說(shuō)所操作的數(shù)據(jù)類(lèi)型被指定為一個(gè)參數(shù)。通俗點(diǎn)將就是“類(lèi)型的變量”。這種類(lèi)型變量可以用在類(lèi)、接口和方法的創(chuàng)建中。理解Java泛型最簡(jiǎn)單的方法是把它看成一種便捷語(yǔ)法,能節(jié)省你某些Java類(lèi)型轉(zhuǎn)換(casting)上的操作:
List<Apple> box = new ArrayList<Apple>();box.add(new Apple()); Apple apple =box.get(0);
上面的代碼自身已表達(dá)的很清楚:box是一個(gè)裝有Apple對(duì)象的List。get方法返回一個(gè)Apple對(duì)象實(shí)例,這個(gè)過(guò)程不需要進(jìn)行類(lèi)型轉(zhuǎn)換。沒(méi)有泛型,上面的代碼需要寫(xiě)成這樣:
Apple apple = (Apple)box.get(0);
當(dāng)然,泛型絕不像我在這里描述的這么簡(jiǎn)單,但這不是我們今天的主角,對(duì)于泛型還不是很明白的同學(xué)需要補(bǔ)課了~當(dāng)然,最好的參考資料還是官方文檔。
2. 泛型帶來(lái)的問(wèn)題(Java 7之前)
泛型的最大優(yōu)點(diǎn)是提供了程序的類(lèi)型安全同時(shí)可以向后兼容,但也有讓開(kāi)發(fā)者不爽的地方,就是每次定義時(shí)都要寫(xiě)明泛型的類(lèi)型,這樣顯示指定不僅感覺(jué)有些冗長(zhǎng),最主要是很多程序員不熟悉泛型,因此很多時(shí)候不能夠給出正確的類(lèi)型參數(shù),現(xiàn)在通過(guò)編譯器自動(dòng)推斷泛型的參數(shù)類(lèi)型,能夠減少這樣的情況,并提高代碼可讀性。
3. Java 7中對(duì)于泛型的類(lèi)型推導(dǎo)方面的改進(jìn)
在Java 7以前的版本中使用泛型類(lèi)型,需要在聲明并賦值的時(shí)候,兩側(cè)都加上泛型類(lèi)型。比方說(shuō)這樣:
Map<String,Integer> map = new HashMap<String,Integer>();
很多人當(dāng)初肯定和我一樣,對(duì)此感到很不解:我在變量聲明中不是已經(jīng)聲明了參數(shù)類(lèi)型了嗎?為什么在對(duì)象初始化的時(shí)候還要顯示的寫(xiě)出來(lái)?這也是泛型在一開(kāi)始出現(xiàn)的時(shí)候受到很多人吐槽的地方。不過(guò),讓人欣慰的是,java在進(jìn)步的同時(shí),那些設(shè)計(jì)者們也在不斷的改進(jìn)java的編譯器,讓它變的更加智能與人性化。這里,就是我們今天的主角:類(lèi)型推倒...額...不是推倒,是類(lèi)型推導(dǎo),即type inference,這哥們兒的出現(xiàn),再寫(xiě)上面這樣的代碼的時(shí)候,可以很開(kāi)心地省略掉對(duì)象實(shí)例化時(shí)的參數(shù)類(lèi)型,也就變成了這個(gè)樣子:
Map<String,Integer> map = new HashMap<>();
在這條語(yǔ)句中,編譯器會(huì)根據(jù)變量聲明時(shí)的泛型類(lèi)型自動(dòng)推斷出實(shí)例化HashMap時(shí)的泛型類(lèi)型。再次提醒一定要注意new HashMap后面的“<>”,只有加上這個(gè)“<>”才表示是自動(dòng)類(lèi)型推斷,否則就是非泛型類(lèi)型的HashMap,并且在使用編譯器編譯源代碼時(shí)會(huì)給出一個(gè)警告提示(unchecked conversion warning)。這一對(duì)尖括號(hào)"<>"官方文檔中叫做"diamond"。
但是,這時(shí)候的類(lèi)型推導(dǎo)做的并不完全(甚至算是一個(gè)半成品),因?yàn)樵贘ava SE 7中創(chuàng)建泛型實(shí)例時(shí)的類(lèi)型推斷是有限制的:只有構(gòu)造器的參數(shù)化類(lèi)型在上下文中被顯著的聲明了,才可以使用類(lèi)型推斷,否則不行。例如:下面的例子在java 7無(wú)法正確編譯(但現(xiàn)在在java8里面可以編譯,因?yàn)楦鶕?jù)方法參數(shù)來(lái)自動(dòng)推斷泛型的類(lèi)型):
List<String> list = new ArrayList<>();
list.add("A");// 由于addAll期望獲得Collection<? extends String>類(lèi)型的參數(shù),因此下面的語(yǔ)句無(wú)法通過(guò)
list.addAll(new ArrayList<>());
4. 在Java8中的再進(jìn)化
在最新的java官方文檔之中,我們可以看到對(duì)于類(lèi)型推導(dǎo)的定義:
Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.
簡(jiǎn)言之,類(lèi)型推導(dǎo)也就是指編譯器能夠根據(jù)你調(diào)用的方法和相應(yīng)的聲明來(lái)確定需要的參數(shù)類(lèi)型的能力。并且官方文檔中還給出了一個(gè)例子加以詮釋?zhuān)?/p>
static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());
在這里,編譯器能夠推導(dǎo)出傳入pick方法中的第二個(gè)參數(shù)的類(lèi)型是Serializable的。
在之前的java版本當(dāng)中,上面的例子要能夠通過(guò)編譯的話需要這要寫(xiě):
Serializable s = this.<Serializable>pick("d", new ArrayList<String>());
這樣寫(xiě)的詳細(xì)原因可以在Bruce Eckel的java編程思想(第四版)的泛型一章看得到,當(dāng)然這本書(shū)是基于java6的,這個(gè)版本還沒(méi)有類(lèi)型推導(dǎo)這個(gè)概念??吹竭@里,很多人已經(jīng)明顯能看得出來(lái)最新版本中類(lèi)型推導(dǎo)的強(qiáng)力之處了。已經(jīng)不僅僅局限于泛型類(lèi)的聲明與實(shí)例化過(guò)程了,而是延伸到了具有泛型參數(shù)的方法當(dāng)中了。
4.1 類(lèi)型推導(dǎo)和泛型方法(Type Inference and Generic Methods)
關(guān)于新版本中的類(lèi)型推導(dǎo)和泛型方法,文檔中還給了一個(gè)稍微復(fù)雜一點(diǎn)的例子,我在這里貼出來(lái),原理和上面的Serializable例子都是一樣就不再贅述,想鞏固的可以再看一下:
public class BoxDemo {
public static <U> void addBox(U u,
java.util.List<Box<U>> boxes) {
Box<U> box = new Box<>();
box.set(u);
boxes.add(box);
}
public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
int counter = 0;
for (Box<U> box: boxes) {
U boxContents = box.get();
System.out.println("Box #" + counter + " contains [" +
boxContents.toString() + "]");
counter++;
}
}
public static void main(String[] args) {
java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
new java.util.ArrayList<>();
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
BoxDemo.outputBoxes(listOfIntegerBoxes);
}
}
上面這段代碼輸出為:
Box #0 contains [10] Box #1 contains [20] Box #2 contains [30]
提一下,泛型方法addBox重點(diǎn)就在于在新java版本中你不需要再在方法調(diào)用中進(jìn)行顯示的類(lèi)型說(shuō)明,像這樣:
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
編譯器能夠從傳入addBox中的參數(shù)自動(dòng)推斷出參數(shù)類(lèi)型是Integer.
4.2 類(lèi)型推導(dǎo)與泛型類(lèi)和非泛型類(lèi)的泛型構(gòu)造器(Type Inference and Generic Constructors of Generic and Non-Generic Classes)
額...這個(gè)也許英語(yǔ)的更好斷句一點(diǎn):Type Inference and Generic Constructors of Generic and Non-Generic Classes
其實(shí),泛型構(gòu)造器并不是泛型類(lèi)的專(zhuān)利品,非泛型類(lèi)也完全可以有自己的泛型構(gòu)造器,看一下這個(gè)例子:
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}
假如對(duì) MyClass類(lèi)做出下面這樣的實(shí)例化:
new MyClass<Integer>("")
OK,這里我們顯示地指出了MyClass的泛參類(lèi)型X是Integer,而對(duì)于構(gòu)造器,編譯器根據(jù)傳入的String對(duì)象("")推導(dǎo)出形式參數(shù)T是String,這個(gè)在java7版本之中已經(jīng)實(shí)現(xiàn)了,在Java8中有了什么改進(jìn)呢?在Java8之后,對(duì)于這種具有泛型構(gòu)造器的泛型類(lèi)的實(shí)例化我們可以這么寫(xiě):
MyClass<Integer> myObject = new MyClass<>("");
對(duì),還是這一對(duì)尖括號(hào)(<>),江湖人稱(chēng)diamond,這樣我們的編譯器就能夠自動(dòng)推導(dǎo)出形式參數(shù)X是Integer,T是String了。這個(gè)其實(shí)和我們一開(kāi)始Map<String,String>的例子很像,只是多了個(gè)構(gòu)造器的泛型化。
需要注意的是:類(lèi)型推導(dǎo)只能根據(jù)調(diào)用的參數(shù)類(lèi)型、目標(biāo)類(lèi)型(這個(gè)馬上會(huì)講到)和返回類(lèi)型(如果有返回的話)進(jìn)行推導(dǎo),而不能根據(jù)程序后面的一些需求來(lái)進(jìn)行推導(dǎo)。
4.3 目標(biāo)類(lèi)型(Target Type)
前文已經(jīng)提到過(guò),編譯器能夠根據(jù)目標(biāo)類(lèi)型進(jìn)行類(lèi)型推導(dǎo)。一個(gè)表達(dá)式的目標(biāo)類(lèi)型指的是一種編譯器根據(jù)表達(dá)式出現(xiàn)的位置而需要的正確的數(shù)據(jù)類(lèi)型。比如這個(gè)例子:
static <T> List<T> emptyList(); List<String> listOne = Collections.emptyList();
在這里,List<String>就是目標(biāo)類(lèi)型,因?yàn)檫@里需要的是List<String> ,而Collections.emptyList()返回的是List<T> ,所以這里編譯器就推斷T一定是String。這個(gè)在Java 7 和 8 中都OK。但是在java 7 中,在下面這種情況中就不能正常編譯了:
void processStringList(List<String> stringList) {
// process stringList
}
processStringList(Collections.emptyList());
這個(gè)時(shí)候,java7就會(huì)給出這種錯(cuò)誤提示:
//List<Object> cannot be converted to List<String>
原因:Collections.emptyList() 返回的是List<T> ,這里的T需要一個(gè)具體類(lèi)型,但是因?yàn)椴荒軓姆椒暶髦型茢喑鏊璧氖?code>String,所以編譯器就給T了一個(gè)Object的值,很明顯,List<Object>不能轉(zhuǎn)型到List<String>.所以在java7版本中你需要這樣調(diào)用這個(gè)方法:
processStringList(Collections.<String>emptyList());
但是,在java8中,由于目標(biāo)類(lèi)型概念的引入,這里,很明顯編譯器需要的是List<String> (也就是這里的Target Type),所以編譯器推斷返回的List<T>中的T一定是String,所以processStringList(Collections.emptyList());這種描述是OK的。
目標(biāo)類(lèi)型的使用在Lambda表達(dá)式中優(yōu)勢(shì)最為明顯。
總結(jié)
好了,以上就是關(guān)于java中類(lèi)型推導(dǎo)的一些個(gè)人見(jiàn)解,總結(jié)來(lái)說(shuō),越來(lái)越完善的類(lèi)型推導(dǎo)就是完成了一些本來(lái)就感覺(jué)很理所當(dāng)然的類(lèi)型轉(zhuǎn)換工作,只是這些工作滿(mǎn)滿(mǎn)地全交給了編譯器去自動(dòng)推導(dǎo)而不是讓開(kāi)發(fā)者顯示地去指定。希望這篇文章的內(nèi)容對(duì)大家學(xué)習(xí)Java能有所幫助,如果有疑問(wèn)可以留言交流。
相關(guān)文章
SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Spring @Configuration和@Component的區(qū)別
今天小編就為大家分享一篇關(guān)于Spring @Configuration和@Component的區(qū)別,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
CommonMark 使用教程:將 Markdown 語(yǔ)法轉(zhuǎn)成 Html
這篇文章主要介紹了CommonMark 使用教程:將 Markdown 語(yǔ)法轉(zhuǎn)成 Html,這個(gè)技巧我們做任何網(wǎng)站都可以用到,而且非常好用。,需要的朋友可以參考下2019-06-06
什么是遞歸?用Java寫(xiě)一個(gè)簡(jiǎn)單的遞歸程序
這篇文章主要介紹了什么是遞歸?用Java寫(xiě)一個(gè)簡(jiǎn)單的遞歸程序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
SpringBoot整合RocketMQ的詳細(xì)過(guò)程
這篇文章主要介紹了SpringBoot整合RocketMQ的詳細(xì)過(guò)程,本文分為三部分,第一部分實(shí)現(xiàn)SpringBoot與RocketMQ的整合,第二部分解決在使用RocketMQ過(guò)程中可能遇到的一些問(wèn)題并解決他們,第三部分介紹如何封裝RocketMQ以便更好地使用,需要的朋友可以參考下2023-04-04
Spring整合Mybatis 掃描注解創(chuàng)建Bean報(bào)錯(cuò)的解決方案
這篇文章主要介紹了Spring 整合Mybatis 掃描注解創(chuàng)建Bean報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
SpringBoot中@ConfigurationProperties實(shí)現(xiàn)配置自動(dòng)綁定的方法
本文主要介紹了SpringBoot中@ConfigurationProperties實(shí)現(xiàn)配置自動(dòng)綁定的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02

