1:?jiǎn)枺菏裁词?Java 泛型中的限定通配符和非限定通配符?有什么區(qū)別?
答:限定通配符對(duì)類型進(jìn)行限制,泛型中有兩種限定通配符,一種是 <? extends T> 來保證泛型類型必須是 T 的子類來設(shè)定泛型類型的上邊界,另一種是 <? super T> 來保證泛型類型必須是 T 的父類來設(shè)定類型的下邊界,泛型類型必須用限定內(nèi)的類型來進(jìn)行初始化,否則會(huì)導(dǎo)致編譯錯(cuò)誤。非限定通配符 <?> 表示可以用任意泛型類型來替代,可以在某種意義上來說是泛型向上轉(zhuǎn)型的語法格式,因?yàn)?List<String> 與 List<Object> 不存在繼承關(guān)系。
2:?jiǎn)枺汉?jiǎn)單說說 List<Object> 與 List 原始類型之間的區(qū)別?
答:主要區(qū)別有兩點(diǎn)。
原始類型和帶泛型參數(shù)類型 <Object> 之間的主要區(qū)別是在編譯時(shí)編譯器不會(huì)對(duì)原始類型進(jìn)行類型安全檢查,卻會(huì)對(duì)帶參數(shù)的類型進(jìn)行檢查,通過使用 Object 作為類型可以告知編譯器該方法可以接受任何類型的對(duì)象(比如 String 或 Integer)。
我們可以把任何帶參數(shù)的類型傳遞給原始類型 List,但卻不能把 List<String> 傳遞給接受 List<Object> 的方法,因?yàn)闀?huì)產(chǎn)生編譯錯(cuò)誤。
3:?jiǎn)枺汉?jiǎn)單說說 List<Object> 與 List<?> 類型之間的區(qū)別?
答:這道題跟上一道題看起來很像,實(shí)質(zhì)上卻完全不同。List<?> 是一個(gè)未知類型的 List,而 List<Object> 其實(shí)是任意類型的 List,我們可以把 List<String>、List<Integer> 賦值給 List<?>,卻不能把 List<String> 賦值給 List<Object>。譬如:

所以通配符形式都可以用類型參數(shù)的形式來替代,通配符能做的用類型參數(shù)都能做。 通配符形式可以減少類型參數(shù),形式上往往更為簡(jiǎn)單,可讀性也更好,所以能用通配符的就用通配符。 如果類型參數(shù)之間有依賴關(guān)系或者返回值依賴類型參數(shù)或者需要寫操作則只能用類型參數(shù)。
4:?jiǎn)枺篖ist<? extends T>和List <? super T>之間有什么區(qū)別?
答:有時(shí)面試官會(huì)用這個(gè)問題來評(píng)估你對(duì)泛型的理解,而不是直接問你什么是限定通配符和非限定通配符,這兩個(gè) List 的聲明都是限定通配符的例子,List<? extends T> 可以接受任何繼承自 T 的類型的 List,而 List<? super T> 可以接受任何 T 的父類構(gòu)成的 List。例如 List<? extends Number> 可以接受 List<Integer> 或 List<Float>。Java 容器類的實(shí)現(xiàn)中有很多這種用法,比如 Collections 中就有如下一些方法:

5:?jiǎn)枺赫f說 <T extends E> 和 <? extends E> 有什么區(qū)別?
答:它們用的地方不一樣,<T extends E> 用于定義類型參數(shù),聲明了一個(gè)類型參數(shù) T,可放在泛型類定義中類名后面、接口后面、泛型方法返回值前面。 <? extends E> 用于實(shí)例化類型參數(shù),用于實(shí)例化泛型變量中的類型參數(shù),只是這個(gè)具體類型是未知的,只知道它是 E 或 E 的某個(gè)子類型。雖然它們不一樣,但兩種寫法經(jīng)常可以達(dá)到相同的目的,譬如:

6:?jiǎn)枺赫f說 List<String> 與 List<Object> 的關(guān)系和區(qū)別?
答:這兩個(gè)東西沒有關(guān)系只有區(qū)別。
因?yàn)橐苍S很多人認(rèn)為 String 是 Object 的子類,所以 List<String> 應(yīng)當(dāng)可以用在需要 List<Object> 的地方,但是事實(shí)并非如此,泛型類型之間不具備泛型參數(shù)類型的繼承關(guān)系,所以 List<String> 和 List<Object> 沒有關(guān)系,無法轉(zhuǎn)換。
7:?jiǎn)枺赫?qǐng)說說下面代碼片段中注釋行執(zhí)行結(jié)果和原因?

答:上面代碼段注釋行執(zhí)行情況解釋如下。
三個(gè) add 方法都是非法的,無論是 Integer,還是 Number 或 Object,編譯器都會(huì)報(bào)錯(cuò)。因?yàn)?? 表示類型安全無知,? extends Number 表示是 Number 的某個(gè)子類型,但不知道具體子類型, 如果允許寫入,Java 就無法確保類型安全性,所以直接禁止。
最后方法的 add 是合法的,因?yàn)?<? super E> 形式與 <? extends E> 正好相反,超類型通配符表示 E 的某個(gè)父類型,有了它我們就可以更靈活的寫入了。
本題特別重要:一定要注意泛型類型聲明變量 ?時(shí)寫數(shù)據(jù)的規(guī)則。
8:?jiǎn)枺赫?qǐng)說說下面代碼片段中注釋行執(zhí)行結(jié)果和原因?

答:上面代碼編譯運(yùn)行情況如注釋所示,本題主要考察泛型中的 ? 通配符的上下邊界擴(kuò)展問題。
通配符對(duì)于上邊界有如下限制:Vector<? extends 類型1> x = new Vector<類型2>(); 中的類型1指定一個(gè)數(shù)據(jù)類型,則類型2就只能是類型1或者是類型1的子類。
通配符對(duì)于下邊界有如下限制:Vector<? super 類型1> x = new Vector<類型2>(); 中的類型1指定一個(gè)數(shù)據(jù)類型,則類型2就只能是類型1或者是類型1的父類。
9: 問:下面程序合法嗎?

答:編譯時(shí)報(bào)錯(cuò),因?yàn)?Java 類型參數(shù)限定只有 extends 形式,沒有 super 形式。
10: 問:下面程序有什么問題?該如何修復(fù)?

答:語句 printCollection(listInteger); 編譯報(bào)錯(cuò),因?yàn)榉盒偷膮?shù)是沒有繼承關(guān)系的。修復(fù)方式就是使用 ?通配符,printCollection(Collection<?> collection),因?yàn)樵诜椒?printCollection(Collection<?> collection) 中不可以出現(xiàn)與參數(shù)類型有關(guān)的方法,譬如 collection.add(),因?yàn)槌绦蛘{(diào)用這個(gè)方法的時(shí)候傳入的參數(shù)不知道是什么類型的,但是可以調(diào)用與參數(shù)類型無關(guān)的方法,譬如
collection.size()。
11:?jiǎn)枺赫?qǐng)解釋下面程序片段的執(zhí)行情況及原因?

答:t0 編譯直接報(bào)錯(cuò),add 的兩個(gè)參數(shù)一個(gè)是 Integer,一個(gè)是 Float,所以取同一父類的最小級(jí)為 Number,故 T 為 Number 類型,而 t0 類型為 int,所以類型錯(cuò)誤。
t1 執(zhí)行賦值成功,add 的兩個(gè)參數(shù)都是 Integer,所以 T 為 Integer 類型。
t2 執(zhí)行賦值成功,add 的兩個(gè)參數(shù)一個(gè)是 Integer,一個(gè)是 Float,所以取同一父類的最小級(jí)為 Number,故 T 為 Number 類型。
t3 執(zhí)行賦值成功,add 的兩個(gè)參數(shù)一個(gè)是 Integer,一個(gè)是 Float,所以取同一父類的最小級(jí)為 Object,故 T 為 Object 類型。
t4 執(zhí)行賦值成功,add 指定了泛型類型為 Integer,所以只能 add 為 Integer 類型或者其子類的參數(shù)。
t5 編譯直接報(bào)錯(cuò),add 指定了泛型類型為 Integer,所以只能 add 為 Integer 類型或者其子類的參數(shù),不能為 Float。
t6 執(zhí)行賦值成功,add 指定了泛型類型為 Number,所以只能 add 為 Number 類型或者其子類的參數(shù),Integer 和 Float 均為其子類,所以可以 add 成功。
t0、t1、t2、t3 其實(shí)演示了調(diào)用泛型方法不指定泛型的幾種情況,t4、t5、t6 演示了調(diào)用泛型方法指定泛型的情況。 在調(diào)用泛型方法的時(shí)可以指定泛型,也可以不指定泛型;在不指定泛型時(shí)泛型變量的類型為該方法中的幾種類型的同一個(gè)父類的最小級(jí)(直到 Object),在指定泛型時(shí)該方法中的幾種類型必須是該泛型實(shí)例類型或者其子類。切記,java 編譯器是通過先檢查代碼中泛型的類型,然后再進(jìn)行類型擦除,再進(jìn)行編譯的。
12:?jiǎn)枺合旅鎯蓚€(gè)方法有什么區(qū)別?為什么?

答:get1 方法直接編譯錯(cuò)誤,因?yàn)榫幾g器在編譯前首先進(jìn)行了泛型檢查和泛型擦除才編譯,所以等到真正編譯時(shí) T 由于沒有類型限定自動(dòng)擦除為 Object 類型,所以只能調(diào)用 Object 的方法,而 Object 沒有 compareTo 方法。
get2 方法添加了泛型類型限定可以正常使用,因?yàn)橄薅愋蜑?Comparable 接口,其存在 compareTo 方法,所以 t1、t2 擦除后被強(qiáng)轉(zhuǎn)成功。所以類型限定在泛型類、泛型接口和泛型方法中都可以使用,不過不管該限定是類還是接口都使用 extends 和 & 符號(hào),如果限定類型既有接口也有類則類必須只有一個(gè)且放在首位,如果泛型類型變量有多個(gè)限定則原始類型就用第一個(gè)邊界的類型變量來替換。
基礎(chǔ)知識(shí):
1: 為什么需要泛型?
1) : 類型安全 (在編譯期間判斷類型,類型不對(duì)則不通過)
2) 設(shè)計(jì)通用類型 (提高復(fù)用率)
2: 用在什么地方
1): 用在類上,叫做泛型類
public class Animal<T> {
private T t;
public T getData() {
return t;
}
}
2): 用在接口上,叫做泛型接口
public interface FansInterface<K, V> {
K add(V v);
}
3): 用在方法上,叫做泛型方法, (不一定非要在泛型類中,在普通類中也可以申請(qǐng)泛型方法)
public <V> void test(V v) {
}
3: 泛型注意事項(xiàng)
1): 泛型類型參數(shù)不能是基本類型。
-
: 一旦形參中使用了?通配符,那么除了寫入null以外,不可以調(diào)用任何和泛型參數(shù)有關(guān)的方法,當(dāng)然和泛型參數(shù)無關(guān)的方法是可以調(diào)用的,如:
public static void test(List<?> list) {
int size = list.size(); // 正確list.add(new Integer(1)); // 錯(cuò)誤
}
看上面的形參使用了 <?> 通配符, 所以調(diào)用size()方法是與泛型無關(guān)的,可以調(diào)用,但要調(diào)用add()方法就會(huì)報(bào)錯(cuò),因?yàn)?lt;?>表示不確定的類型,一旦加入新類型后,就無法保證類型的安全性,所以list.add()方法是編譯失敗的。
4: Java泛型無法使用 instanceof關(guān)鍵字,例:
Box<Integer> integerBox = new Box<Integer>();
if (integerBox instanceof Box<Integer>) // 錯(cuò)誤
因?yàn)榫幾g器使用類型擦除, 在運(yùn)行時(shí)不會(huì)跟蹤類型參數(shù), 所以無法使用instanceof關(guān)鍵字,integerBox 在經(jīng)過編譯器類型擦除后, 會(huì)變?yōu)? Box integerBox = new Box(); 所以無法使用instanceof關(guān)鍵字
5: 泛型中的繼承關(guān)系

可以看出 Integer雖然繼承自Number, 但是 Box<Integer>與 Box<Number> 卻不是繼承關(guān)系,
6: 泛型中的通配符

可以看出,通配符可以分為子類型限定,超類型限定和無限定
子類型的限定, 表示類型的上界,類似泛型的類型變量限定,
格式: ? extends X
作用: 1:用于安全的訪問數(shù)據(jù),
2: 可以訪問X 以及 X的子類型
3: 只能寫入null, 其余的類型都無法寫入。