日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Java泛型概念相關(guān)面試題匯總。

 鷹兔牛熊眼 2019-08-16

前語(yǔ):不要為了讀文章而讀文章,一定要帶著問(wèn)題來(lái)讀文章,勤思考。

來(lái)源:http://ick/G2m


問(wèn):Java 的泛型是什么?有什么好處和優(yōu)點(diǎn)?JDK 不同版本的泛型有什么區(qū)別?

答: 泛型是 Java SE 1.5 的新特性,泛型的本質(zhì)是參數(shù)化類(lèi)型,這種參數(shù)類(lèi)型可以用在類(lèi)、接口和方法的創(chuàng)建中,分別稱(chēng)為泛型類(lèi)、泛型接口、泛型方法。在 Java SE 1.5 之前沒(méi)有泛型的情況的下只能通過(guò)對(duì)類(lèi)型 Object 的引用來(lái)實(shí)現(xiàn)參數(shù)的任意化,其帶來(lái)的缺點(diǎn)是要做顯式強(qiáng)制類(lèi)型轉(zhuǎn)換,而這種強(qiáng)制轉(zhuǎn)換編譯期是不做檢查的,容易把問(wèn)題留到運(yùn)行時(shí),所以 泛型的好處是在編譯時(shí)檢查類(lèi)型安全,并且所有的強(qiáng)制轉(zhuǎn)換都是自動(dòng)和隱式的,提高了代碼的重用率,避免在運(yùn)行時(shí)出現(xiàn) ClassCastException。

JDK 1.5 引入了泛型來(lái)允許強(qiáng)類(lèi)型在編譯時(shí)進(jìn)行類(lèi)型檢查;JDK 1.7 泛型實(shí)例化類(lèi)型具備了自動(dòng)推斷能力,譬如 List list = new ArrayList(); 可以寫(xiě)成 List llist = new ArrayList<>(); 了,JDK 具備自動(dòng)推斷能力。下面幾種寫(xiě)法可以說(shuō)是不同版本的兼容性了:


問(wèn):Java 泛型是如何工作的?什么是類(lèi)型擦除?

答: 泛型是通過(guò)類(lèi)型擦除來(lái)實(shí)現(xiàn)的,編譯器在編譯時(shí)擦除了所有泛型類(lèi)型相關(guān)的信息,所以在運(yùn)行時(shí)不存在任何泛型類(lèi)型相關(guān)的信息,譬如 List 在運(yùn)行時(shí)僅用一個(gè) List 來(lái)表示,這樣做的目的是為了和 Java 1.5 之前版本進(jìn)行兼容。泛型擦除具體來(lái)說(shuō)就是在編譯成字節(jié)碼時(shí)首先進(jìn)行類(lèi)型檢查,接著進(jìn)行類(lèi)型擦除(即所有類(lèi)型參數(shù)都用他們的限定類(lèi)型替換,包括類(lèi)、變量和方法),接著如果類(lèi)型擦除和多態(tài)性發(fā)生沖突時(shí)就在子類(lèi)中生成橋方法解決,接著如果調(diào)用泛型方法的返回類(lèi)型被擦除則在調(diào)用該方法時(shí)插入強(qiáng)制類(lèi)型轉(zhuǎn)換。


問(wèn):Java 泛型類(lèi)、泛型接口、泛型方法有什么區(qū)別?

答: 泛型類(lèi)是在實(shí)例化類(lèi)的對(duì)象時(shí)才能確定的類(lèi)型,其定義譬如 class Test {},在實(shí)例化該類(lèi)時(shí)必須指明泛型 T 的具體類(lèi)型。

泛型接口與泛型類(lèi)一樣,其定義譬如 interface Generator { E dunc(E e); }。

泛型方法所在的類(lèi)可以是泛型類(lèi)也可以是非泛型類(lèi),是否擁有泛型方法與所在的類(lèi)無(wú)關(guān),所以在我們應(yīng)用中應(yīng)該盡可能使用泛型方法,不要放大作用空間,尤其是在 static 方法時(shí) static 方法無(wú)法訪(fǎng)問(wèn)泛型類(lèi)的類(lèi)型參數(shù),所以更應(yīng)該使用泛型的 static 方法(聲明泛型一定要寫(xiě)在 static 后返回值類(lèi)型前)。泛型方法的定義譬如 void func(T val) {}。


問(wèn):Java 如何優(yōu)雅的實(shí)現(xiàn)元組?

答: 元組其實(shí)是關(guān)系數(shù)據(jù)庫(kù)中的一個(gè)學(xué)術(shù)名詞,一條記錄就是一個(gè)元組,一個(gè)表就是一個(gè)關(guān)系,紀(jì)錄組成表,元組生成關(guān)系,這就是關(guān)系數(shù)據(jù)庫(kù)的核心理念。很多語(yǔ)言天生支持元組,譬如 Python 等,在語(yǔ)法本身支持元組的語(yǔ)言中元組是用括號(hào)表示的,如 (int, bool, string) 就是一個(gè)三元組類(lèi)型,不過(guò)在 Java、C 等語(yǔ)言中就比較坑爹,語(yǔ)言語(yǔ)法本身不具備這個(gè)特性,所以在 Java 中我們?nèi)绻雰?yōu)雅實(shí)現(xiàn)元組就可以借助泛型類(lèi)實(shí)現(xiàn),如下是一個(gè)三元組類(lèi)型的實(shí)現(xiàn):

問(wèn):下面程序塊的運(yùn)行結(jié)果是什么,為什么?

答: 上面代碼段結(jié)果為 true,解釋如下。

因?yàn)?load 的是同一個(gè) class 文件,存在 ArrayList.class 文件但是不存在 ArrayList.class 文件,即便是通過(guò) class.getTypeParameters() 方法獲取類(lèi)型信息也只能獲取到 [T] 一樣的泛型參數(shù)占位符。泛型是通過(guò)擦除來(lái)實(shí)現(xiàn)的,所以編譯后任何具體的泛型類(lèi)型都被擦除了(替換為非泛型上邊界,如果沒(méi)有指定邊界則為 Object 類(lèi)型),泛型類(lèi)型只有在靜態(tài)類(lèi)型檢查期間才出現(xiàn),上面都被擦除成了 ArrayList 類(lèi)型,所以運(yùn)行時(shí)加載的是同一個(gè) class 文件。


問(wèn):為什么 Java 泛型要通過(guò)擦除來(lái)實(shí)現(xiàn)?擦除有什么壞處或者說(shuō)代價(jià)?

答: 可以說(shuō) Java 泛型的存在就是一個(gè)不得已的妥協(xié),正因?yàn)檫@種妥協(xié)導(dǎo)致了 Java 泛型的混亂,甚至說(shuō)是 JDK 泛型設(shè)計(jì)的失敗。Java 之所以要通過(guò)擦除來(lái)實(shí)現(xiàn)泛型機(jī)制其實(shí)是為了兼容性考慮,只有這樣才能讓非泛化代碼到泛化代碼的轉(zhuǎn)變過(guò)程建立在不破壞現(xiàn)有類(lèi)庫(kù)的實(shí)現(xiàn)上。正是因?yàn)檫@種兼容也帶來(lái)了一些代價(jià),譬如泛型不能顯式地引用運(yùn)行時(shí)類(lèi)型的操作之中(如向上向下轉(zhuǎn)型、instanceof 操作等),因?yàn)樗嘘P(guān)于參數(shù)的信息都丟失了,所以任何時(shí)候使用泛型都要提醒自己背后的真實(shí)擦除類(lèi)型到底是什么;此外擦除和兼容性導(dǎo)致了使用泛型并不是強(qiáng)制的(如 List list = new ArrayList(); 等寫(xiě)法);其次擦除會(huì)導(dǎo)致我們?cè)诰帉?xiě)代碼時(shí)十分謹(jǐn)慎(如不想被擦除為 Object 類(lèi)型時(shí)不要忘了添加上邊界操作等)。


問(wèn):下面三個(gè) funcX 方法有問(wèn)題嗎,為什么?

答:func1、func2、func3 三個(gè)方法均無(wú)法編譯通過(guò)。

因?yàn)榉盒筒脸齺G失了在泛型代碼中執(zhí)行某些操作的能力,任何在運(yùn)行時(shí)需要知道確切類(lèi)型信息的操作都將無(wú)法工作。

問(wèn):下面代碼段有問(wèn)題嗎,運(yùn)行效果是什么,為什么?

答: 由于在程序中定義的 ArrayList 泛型類(lèi)型實(shí)例化為 Integer 的對(duì)象,如果直接調(diào)用 add 方法則只能存儲(chǔ)整形數(shù)據(jù),不過(guò)當(dāng)我們利用反射調(diào)用 add 方法時(shí)就可以存儲(chǔ)字符串,因?yàn)?Integer 泛型實(shí)例在編譯之后被擦除了,只保留了原始類(lèi)型 Object,所以自然可以插入。

問(wèn):請(qǐng)比較深入的談?wù)勀銓?duì) Java 泛型擦除的理解和帶來(lái)的問(wèn)題認(rèn)識(shí)?

答:Java 的泛型是偽泛型,因?yàn)樵诰幾g期間所有的泛型信息都會(huì)被擦除掉,譬如 List 在運(yùn)行時(shí)僅用一個(gè) List 來(lái)表示(所以我們可以通過(guò)反射 add 方法來(lái)向 Integer 的泛型列表添加字符串,因?yàn)榫幾g后都成了 Object),這樣做的目的是為了和 Java 1.5 之前版本進(jìn)行兼容。泛型擦除具體來(lái)說(shuō)就是在編譯成字節(jié)碼時(shí)首先進(jìn)行類(lèi)型檢查,接著進(jìn)行類(lèi)型擦除(即所有類(lèi)型參數(shù)都用他們的限定類(lèi)型替換,包括類(lèi)、變量和方法,如果類(lèi)型變量有限定則原始類(lèi)型就用第一個(gè)邊界的類(lèi)型來(lái)替換,譬如 class Prd<T extends Comparable & Serializable> {} 的原始類(lèi)型就是 Comparable),接著如果類(lèi)型擦除和多態(tài)性發(fā)生沖突時(shí)就在子類(lèi)中生成橋方法解決,接著如果調(diào)用泛型方法的返回類(lèi)型被擦除則在調(diào)用該方法時(shí)插入強(qiáng)制類(lèi)型轉(zhuǎn)換。

先檢查再擦除的類(lèi)型檢查是針對(duì)引用的,用引用調(diào)用泛型方法就會(huì)對(duì)這個(gè)引用調(diào)用的方法進(jìn)行類(lèi)型檢測(cè)而無(wú)關(guān)它真正引用的對(duì)象。可以說(shuō)這是為了兼容帶來(lái)的問(wèn)題,如下:

所以說(shuō)擦除前的類(lèi)型檢查是針對(duì)引用的,用這個(gè)引用調(diào)用泛型方法就會(huì)對(duì)這個(gè)引用調(diào)用的方法進(jìn)行類(lèi)型檢測(cè)而無(wú)關(guān)它真正引用的對(duì)象。

先檢查再擦除帶來(lái)的另一個(gè)問(wèn)題就是泛型中參數(shù)化類(lèi)型無(wú)法支持繼承關(guān)系,因?yàn)榉盒偷脑O(shè)計(jì)初衷就是為了解決 Object 類(lèi)型轉(zhuǎn)換的弊端而存在,如果泛型中參數(shù)化類(lèi)型支持繼承操作就違背了設(shè)計(jì)的初衷而繼續(xù)回到原始的 Object 類(lèi)型轉(zhuǎn)換弊端。也同樣可以說(shuō)這是為了兼容帶來(lái)的問(wèn)題,如下:

之所以這樣我們可以從反面來(lái)論證,假設(shè)編譯不報(bào)錯(cuò)則當(dāng)通過(guò) arrayList2 調(diào)用 get() 方法取值時(shí)返回的是 String 類(lèi)型的對(duì)象(因?yàn)轭?lèi)型檢測(cè)是根據(jù)引用來(lái)決定的),而實(shí)際上存放的是 Object 類(lèi)型的對(duì)象,這樣 get 出來(lái)就會(huì) ClassCastException 了,所以這違背了泛型的初衷。對(duì)于 arrayList4 同樣假設(shè)編譯不報(bào)錯(cuò),當(dāng)調(diào)用 arrayList4 的 get() 方法取出來(lái)的 String 變成了 Object 雖然不會(huì)出現(xiàn) ClassCastException,但是依然沒(méi)有意義啊,泛型出現(xiàn)的原因就是為了解決類(lèi)型轉(zhuǎn)換的問(wèn)題,其次如果我們通過(guò) arrayList4 的 add() 方法繼續(xù)添加對(duì)象則可以添加任意類(lèi)型對(duì)象實(shí)例,這就會(huì)導(dǎo)致我們 get() 時(shí)更加懵逼不知道加的是什么類(lèi)型了,所以怎么說(shuō)都是個(gè)死循環(huán)。

擦除帶來(lái)的另一個(gè)問(wèn)題就是泛型與多態(tài)的沖突,其通過(guò)子類(lèi)中生成橋方法解決了多態(tài)沖突問(wèn)題,這個(gè)問(wèn)題的驗(yàn)證也很簡(jiǎn)單,可以通過(guò)下面的例子說(shuō)明:

上面代碼段的運(yùn)行情況很詫異吧,按理來(lái)說(shuō) Creater 類(lèi)被編譯擦除后 setValue 方法的參數(shù)應(yīng)該是 Object 類(lèi)型了,子類(lèi) StringCreater 的 setValue 方法參數(shù)類(lèi)型為 String,看起來(lái)父子類(lèi)的這組方法應(yīng)該是重載關(guān)系,所以調(diào)用子類(lèi)的 setValue 方法添加字符串和 Object 類(lèi)型參數(shù)應(yīng)該都是合法才對(duì),然而從編譯來(lái)看子類(lèi)根本沒(méi)有繼承自父類(lèi)參數(shù)為 Object 類(lèi)型的 setValue 方法,所以說(shuō)子類(lèi)的 setValue 方法是對(duì)父類(lèi)的重寫(xiě)而不是重載(從子類(lèi)添加 @Override 注解沒(méi)報(bào)錯(cuò)也能說(shuō)明是重寫(xiě)關(guān)系)。關(guān)于出現(xiàn)上面現(xiàn)象的原理其實(shí)我們通過(guò) javap 看下兩個(gè)類(lèi)編譯后的本質(zhì)即可:

通過(guò)編譯后的字節(jié)碼我們可以看到 Creater 泛型類(lèi)在編譯后類(lèi)型被擦除為 Object,而我們子類(lèi)的本意是進(jìn)行重寫(xiě)實(shí)現(xiàn)多態(tài),可類(lèi)型擦除后子類(lèi)就和多態(tài)產(chǎn)生了沖突,所以編譯后的字節(jié)碼里就出現(xiàn)了橋方法來(lái)實(shí)現(xiàn)多態(tài)。可以看到橋方法的參數(shù)類(lèi)型都是 Object,也就是說(shuō)子類(lèi)中真正覆蓋父類(lèi)方法的是橋方法,而子類(lèi) String 參數(shù) setValue、getValue 方法上的 @Oveerride 注解只是個(gè)假象,橋方法的內(nèi)部實(shí)現(xiàn)是直接調(diào)用了我們自己重寫(xiě)的那兩個(gè)方法;不過(guò)上面的 setValue 方法是為了解決類(lèi)型擦除與多態(tài)之間的沖突生成的橋方法,而 getValue 是一種協(xié)變,之所以子類(lèi)中 Object getValue() 和 String getValue() 方法可以同時(shí)存在是虛擬機(jī)內(nèi)部的一種區(qū)分(我們自己寫(xiě)的代碼是不允許這樣的),因?yàn)樘摂M機(jī)內(nèi)部是通過(guò)參數(shù)類(lèi)型和返回類(lèi)型來(lái)確定一個(gè)方法簽名的,所以編譯器為了實(shí)現(xiàn)泛型的多態(tài)允許自己做這個(gè)看起來(lái)不合法的實(shí)現(xiàn),實(shí)質(zhì)還是交給了虛擬機(jī)去區(qū)別。

先檢查再擦除帶來(lái)的另一個(gè)問(wèn)題就是泛型讀取時(shí)會(huì)進(jìn)行自動(dòng)類(lèi)型轉(zhuǎn)換問(wèn)題,所以如果調(diào)用泛型方法的返回類(lèi)型被擦除則在調(diào)用該方法時(shí)插入強(qiáng)制類(lèi)型轉(zhuǎn)換。

關(guān)于這個(gè)可以通過(guò) javap 去查看使用 List 的 add、get 方法后的字節(jié)碼指令,你會(huì)發(fā)現(xiàn) checkcast 指令不是在 get 方法里面強(qiáng)轉(zhuǎn)的(雖然 get 方法里面返回值在代碼里面是被轉(zhuǎn)換成了 T,實(shí)際編譯擦除了),而是在調(diào)用處強(qiáng)轉(zhuǎn)的。

擦除帶來(lái)的另一個(gè)問(wèn)題是泛型類(lèi)型參數(shù)不能是基本類(lèi)型,比如 ArrayList 是不合法的,只有 ArrayList 是合法的,因?yàn)楫?dāng)類(lèi)型擦除后 ArrayList 的原始類(lèi)型是 Object,而 Object 是引用類(lèi)型而不是基本類(lèi)型。

擦除帶來(lái)的另一個(gè)問(wèn)題是無(wú)法進(jìn)行具體泛型參數(shù)類(lèi)型的運(yùn)行時(shí)類(lèi)型檢查,譬如 arrayList instanceof ArrayList 是非法的,Java 對(duì)于泛型運(yùn)行時(shí)檢查的支持僅限于 arrayList instanceof ArrayList<?> 方式。

擦除帶來(lái)的另一個(gè)問(wèn)題是我們不能拋出也不能捕獲泛型類(lèi)的對(duì)象,因?yàn)楫惓J窃谶\(yùn)行時(shí)捕獲和拋出的,而在編譯時(shí)泛型信息會(huì)被擦除掉,擦除后兩個(gè) catch 會(huì)變成一樣的東西。也不能在 catch 子句中使用泛型變量,因?yàn)榉盒托畔⒃诰幾g時(shí)已經(jīng)替換為原始類(lèi)型(譬如 catch(T) 在限定符情況下會(huì)變?yōu)樵碱?lèi)型 Throwable),如果可以在 catch 子句中使用則違背了異常的捕獲優(yōu)先級(jí)順序。

問(wèn):為什么 Java 的泛型數(shù)組不能采用具體的泛型類(lèi)型進(jìn)行初始化?

答: 這個(gè)問(wèn)題可以通過(guò)一個(gè)例子來(lái)說(shuō)明。

由于 JVM 泛型的擦除機(jī)制,所以上面代碼可以給 oa[1] 賦值為 ArrayList 也不會(huì)出現(xiàn)異常,但是在取出數(shù)據(jù)的時(shí)候卻要做一次類(lèi)型轉(zhuǎn)換,所以就會(huì)出現(xiàn) ClassCastException,如果可以進(jìn)行泛型數(shù)組的聲明則上面說(shuō)的這種情況在編譯期不會(huì)出現(xiàn)任何警告和錯(cuò)誤,只有在運(yùn)行時(shí)才會(huì)出錯(cuò),但是泛型的出現(xiàn)就是為了消滅 ClassCastException,所以如果 Java 支持泛型數(shù)組初始化操作就是搬起石頭砸自己的腳。而對(duì)于下面的代碼來(lái)說(shuō)是成立的:

所以說(shuō)采用通配符的方式初始化泛型數(shù)組是允許的,因?yàn)閷?duì)于通配符的方式最后取出數(shù)據(jù)是要做顯式類(lèi)型轉(zhuǎn)換的,符合預(yù)期邏輯。綜述就是說(shuō)Java 的泛型數(shù)組初始化時(shí)數(shù)組類(lèi)型不能是具體的泛型類(lèi)型,只能是通配符的形式,因?yàn)榫唧w類(lèi)型會(huì)導(dǎo)致可存入任意類(lèi)型對(duì)象,在取出時(shí)會(huì)發(fā)生類(lèi)型轉(zhuǎn)換異常,會(huì)與泛型的設(shè)計(jì)思想沖突,而通配符形式本來(lái)就需要自己強(qiáng)轉(zhuǎn),符合預(yù)期。

關(guān)于這道題的答案其 Oracle 官方文檔給出了原因:https://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html

問(wèn):下面語(yǔ)句哪些是有問(wèn)題,哪些沒(méi)有問(wèn)題?

答: 上面每個(gè)語(yǔ)句的問(wèn)題注釋部分已經(jīng)闡明了,因?yàn)樵?Java 中是不能創(chuàng)建一個(gè)確切的泛型類(lèi)型的數(shù)組的,除非是采用通配符的方式且要做顯式類(lèi)型轉(zhuǎn)換才可以。

問(wèn):如何正確的初始化泛型數(shù)組實(shí)例?

答: 這個(gè)無(wú)論我們通過(guò) new ArrayList[10] 的形式還是通過(guò)泛型通配符的形式初始化泛型數(shù)組實(shí)例都是存在警告的,也就是說(shuō)僅僅語(yǔ)法合格,運(yùn)行時(shí)潛在的風(fēng)險(xiǎn)需要我們自己來(lái)承擔(dān),因此那些方式初始化泛型數(shù)組都不是最優(yōu)雅的方式,我們?cè)谑褂玫椒盒蛿?shù)組的場(chǎng)景下應(yīng)該盡量使用列表集合替換,此外也可以通過(guò)使用 java.lang.reflect.Array.newInstance(Class componentType, int length) 方法來(lái)創(chuàng)建一個(gè)具有指定類(lèi)型和維度的數(shù)組,如下:

所以使用反射來(lái)初始化泛型數(shù)組算是優(yōu)雅實(shí)現(xiàn),因?yàn)榉盒皖?lèi)型 T 在運(yùn)行時(shí)才能被確定下來(lái),我們能創(chuàng)建泛型數(shù)組也必然是在 Java 運(yùn)行時(shí)想辦法,而運(yùn)行時(shí)能起作用的技術(shù)最好的就是反射了。


問(wèn):Java 泛型對(duì)象能實(shí)例化 T t = new T() 嗎,為什么?

答: 不能,因?yàn)樵?Java 編譯期沒(méi)法確定泛型參數(shù)化類(lèi)型,也就找不到對(duì)應(yīng)的類(lèi)字節(jié)碼文件,所以自然就不行了,此外由于 T 被擦除為 Object,如果可以 new T() 則就變成了 new Object(),失去了本意。如果要實(shí)例化一個(gè)泛型 T 則可以通過(guò)反射實(shí)現(xiàn)(實(shí)例化泛型數(shù)組也類(lèi)似),如下:

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多