1:問:什么是 Java 泛型中的限定通配符和非限定通配符锈拨?有什么區(qū)別?
答:限定通配符對類型進行限制蔫缸,泛型中有兩種限定通配符惋鸥,一種是 <? extends T> 來保證泛型類型必須是 T 的子類來設(shè)定泛型類型的上邊界,另一種是 <? super T> 來保證泛型類型必須是 T 的父類來設(shè)定類型的下邊界鸟废,泛型類型必須用限定內(nèi)的類型來進行初始化猜敢,否則會導(dǎo)致編譯錯誤。非限定通配符 <?> 表示可以用任意泛型類型來替代盒延,可以在某種意義上來說是泛型向上轉(zhuǎn)型的語法格式缩擂,因為 List<String> 與 List<Object> 不存在繼承關(guān)系。
2:問:簡單說說 List<Object> 與 List 原始類型之間的區(qū)別添寺?
答:主要區(qū)別有兩點撇叁。
原始類型和帶泛型參數(shù)類型 <Object> 之間的主要區(qū)別是在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數(shù)的類型進行檢查畦贸,通過使用 Object 作為類型可以告知編譯器該方法可以接受任何類型的對象(比如 String 或 Integer)陨闹。
我們可以把任何帶參數(shù)的類型傳遞給原始類型 List,但卻不能把 List<String> 傳遞給接受 List<Object> 的方法薄坏,因為會產(chǎn)生編譯錯誤趋厉。
3:問:簡單說說 List<Object> 與 List<?> 類型之間的區(qū)別?
答:這道題跟上一道題看起來很像胶坠,實質(zhì)上卻完全不同君账。List<?> 是一個未知類型的 List,而 List<Object> 其實是任意類型的 List沈善,我們可以把 List<String>乡数、List<Integer> 賦值給 List<?>,卻不能把 List<String> 賦值給 List<Object>闻牡。譬如:
所以通配符形式都可以用類型參數(shù)的形式來替代净赴,通配符能做的用類型參數(shù)都能做。 通配符形式可以減少類型參數(shù)罩润,形式上往往更為簡單玖翅,可讀性也更好,所以能用通配符的就用通配符割以。 如果類型參數(shù)之間有依賴關(guān)系或者返回值依賴類型參數(shù)或者需要寫操作則只能用類型參數(shù)金度。
4:問:List<? extends T>和List <? super T>之間有什么區(qū)別?
答:有時面試官會用這個問題來評估你對泛型的理解严沥,而不是直接問你什么是限定通配符和非限定通配符猜极,這兩個 List 的聲明都是限定通配符的例子,List<? extends T> 可以接受任何繼承自 T 的類型的 List消玄,而 List<? super T> 可以接受任何 T 的父類構(gòu)成的 List跟伏。例如 List<? extends Number> 可以接受 List<Integer> 或 List<Float>丢胚。Java 容器類的實現(xiàn)中有很多這種用法,比如 Collections 中就有如下一些方法:
5:問:說說 <T extends E> 和 <? extends E> 有什么區(qū)別酬姆?
答:它們用的地方不一樣嗜桌,<T extends E> 用于定義類型參數(shù),聲明了一個類型參數(shù) T辞色,可放在泛型類定義中類名后面骨宠、接口后面、泛型方法返回值前面相满。 <? extends E> 用于實例化類型參數(shù)层亿,用于實例化泛型變量中的類型參數(shù),只是這個具體類型是未知的立美,只知道它是 E 或 E 的某個子類型匿又。雖然它們不一樣,但兩種寫法經(jīng)辰ㄌ悖可以達到相同的目的碌更,譬如:
6:問:說說 List<String> 與 List<Object> 的關(guān)系和區(qū)別?
答:這兩個東西沒有關(guān)系只有區(qū)別洞慎。
因為也許很多人認為 String 是 Object 的子類痛单,所以 List<String> 應(yīng)當可以用在需要 List<Object> 的地方,但是事實并非如此劲腿,泛型類型之間不具備泛型參數(shù)類型的繼承關(guān)系旭绒,所以 List<String> 和 List<Object> 沒有關(guān)系,無法轉(zhuǎn)換焦人。
7:問:請說說下面代碼片段中注釋行執(zhí)行結(jié)果和原因硫兰?
答:上面代碼段注釋行執(zhí)行情況解釋如下逝嚎。
三個 add 方法都是非法的泥张,無論是 Integer疲陕,還是 Number 或 Object,編譯器都會報錯个从。因為 ? 表示類型安全無知脉幢,? extends Number 表示是 Number 的某個子類型,但不知道具體子類型嗦锐, 如果允許寫入,Java 就無法確保類型安全性沪曙,所以直接禁止奕污。
最后方法的 add 是合法的,因為 <? super E> 形式與 <? extends E> 正好相反液走,超類型通配符表示 E 的某個父類型碳默,有了它我們就可以更靈活的寫入了贾陷。
本題特別重要:一定要注意泛型類型聲明變量 ?時寫數(shù)據(jù)的規(guī)則嘱根。
8:問:請說說下面代碼片段中注釋行執(zhí)行結(jié)果和原因髓废?
答:上面代碼編譯運行情況如注釋所示,本題主要考察泛型中的 ? 通配符的上下邊界擴展問題该抒。
通配符對于上邊界有如下限制:Vector<? extends 類型1> x = new Vector<類型2>(); 中的類型1指定一個數(shù)據(jù)類型慌洪,則類型2就只能是類型1或者是類型1的子類。
通配符對于下邊界有如下限制:Vector<? super 類型1> x = new Vector<類型2>(); 中的類型1指定一個數(shù)據(jù)類型凑保,則類型2就只能是類型1或者是類型1的父類冈爹。
9: 問:下面程序合法嗎?
答:編譯時報錯欧引,因為 Java 類型參數(shù)限定只有 extends 形式频伤,沒有 super 形式。
10: 問:下面程序有什么問題芝此?該如何修復(fù)憋肖?
答:語句 printCollection(listInteger); 編譯報錯,因為泛型的參數(shù)是沒有繼承關(guān)系的婚苹。修復(fù)方式就是使用 岸更?通配符,printCollection(Collection<?> collection)租副,因為在方法 printCollection(Collection<?> collection) 中不可以出現(xiàn)與參數(shù)類型有關(guān)的方法坐慰,譬如 collection.add(),因為程序調(diào)用這個方法的時候傳入的參數(shù)不知道是什么類型的用僧,但是可以調(diào)用與參數(shù)類型無關(guān)的方法结胀,譬如
collection.size()。
11:問:請解釋下面程序片段的執(zhí)行情況及原因责循?
答:t0 編譯直接報錯糟港,add 的兩個參數(shù)一個是 Integer,一個是 Float院仿,所以取同一父類的最小級為 Number秸抚,故 T 為 Number 類型,而 t0 類型為 int歹垫,所以類型錯誤剥汤。
t1 執(zhí)行賦值成功,add 的兩個參數(shù)都是 Integer排惨,所以 T 為 Integer 類型吭敢。
t2 執(zhí)行賦值成功,add 的兩個參數(shù)一個是 Integer暮芭,一個是 Float鹿驼,所以取同一父類的最小級為 Number欲低,故 T 為 Number 類型。
t3 執(zhí)行賦值成功畜晰,add 的兩個參數(shù)一個是 Integer砾莱,一個是 Float,所以取同一父類的最小級為 Object凄鼻,故 T 為 Object 類型腊瑟。
t4 執(zhí)行賦值成功,add 指定了泛型類型為 Integer野宜,所以只能 add 為 Integer 類型或者其子類的參數(shù)扫步。
t5 編譯直接報錯,add 指定了泛型類型為 Integer匈子,所以只能 add 為 Integer 類型或者其子類的參數(shù)河胎,不能為 Float。
t6 執(zhí)行賦值成功虎敦,add 指定了泛型類型為 Number游岳,所以只能 add 為 Number 類型或者其子類的參數(shù),Integer 和 Float 均為其子類其徙,所以可以 add 成功胚迫。
t0、t1唾那、t2访锻、t3 其實演示了調(diào)用泛型方法不指定泛型的幾種情況,t4闹获、t5期犬、t6 演示了調(diào)用泛型方法指定泛型的情況。 在調(diào)用泛型方法的時可以指定泛型避诽,也可以不指定泛型龟虎;在不指定泛型時泛型變量的類型為該方法中的幾種類型的同一個父類的最小級(直到 Object),在指定泛型時該方法中的幾種類型必須是該泛型實例類型或者其子類沙庐。切記鲤妥,java 編譯器是通過先檢查代碼中泛型的類型,然后再進行類型擦除拱雏,再進行編譯的棉安。
12:問:下面兩個方法有什么區(qū)別?為什么铸抑?
答:get1 方法直接編譯錯誤垂券,因為編譯器在編譯前首先進行了泛型檢查和泛型擦除才編譯,所以等到真正編譯時 T 由于沒有類型限定自動擦除為 Object 類型羡滑,所以只能調(diào)用 Object 的方法菇爪,而 Object 沒有 compareTo 方法。
get2 方法添加了泛型類型限定可以正常使用柒昏,因為限定類型為 Comparable 接口凳宙,其存在 compareTo 方法,所以 t1职祷、t2 擦除后被強轉(zhuǎn)成功氏涩。所以類型限定在泛型類、泛型接口和泛型方法中都可以使用有梆,不過不管該限定是類還是接口都使用 extends 和 & 符號是尖,如果限定類型既有接口也有類則類必須只有一個且放在首位,如果泛型類型變量有多個限定則原始類型就用第一個邊界的類型變量來替換泥耀。
基礎(chǔ)知識:
1: 為什么需要泛型饺汹?
1) : 類型安全 (在編譯期間判斷類型,類型不對則不通過)
2) 設(shè)計通用類型 (提高復(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): 用在方法上,叫做泛型方法夸溶, (不一定非要在泛型類中逸吵,在普通類中也可以申請泛型方法)
public <V> void test(V v) {
}
3: 泛型注意事項
1): 泛型類型參數(shù)不能是基本類型。
-
: 一旦形參中使用了缝裁?通配符扫皱,那么除了寫入null以外,不可以調(diào)用任何和泛型參數(shù)有關(guān)的方法捷绑,當然和泛型參數(shù)無關(guān)的方法是可以調(diào)用的韩脑,如:
public static void test(List<?> list) {
int size = list.size(); // 正確list.add(new Integer(1)); // 錯誤
}
看上面的形參使用了 <?> 通配符, 所以調(diào)用size()方法是與泛型無關(guān)的胎食,可以調(diào)用扰才,但要調(diào)用add()方法就會報錯,因為<?>表示不確定的類型厕怜,一旦加入新類型后衩匣,就無法保證類型的安全性,所以list.add()方法是編譯失敗的粥航。
4: Java泛型無法使用 instanceof關(guān)鍵字琅捏,例:
Box<Integer> integerBox = new Box<Integer>();
if (integerBox instanceof Box<Integer>) // 錯誤
因為編譯器使用類型擦除, 在運行時不會跟蹤類型參數(shù), 所以無法使用instanceof關(guān)鍵字,integerBox 在經(jīng)過編譯器類型擦除后递雀, 會變?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滤奈, 其余的類型都無法寫入摆昧。