Java泛型常見面試題(面試必問)
1、泛型的基礎(chǔ)概念
1.1 為什么需要泛型
List list = new ArrayList();//默認(rèn)類型是Object list.add("A123"); list.add("B234"); list.add("C345"); System.out.println(list); for(int i=0;i<list.size();i++){ //若要將list中的元素賦給String變量,需要進(jìn)行類型轉(zhuǎn)換,不然會(huì)報(bào)Incompatible types錯(cuò)誤,顯示list.get(i)返回的是Object String str = (String) list.get(i); System.out.println(str); } list.add(123);//因?yàn)轭愋褪荗bject,我們可以把Integer類型或者其他數(shù)據(jù)類型的元素也加入list之中 System.out.println(list.get(3)); for(int i=0;i<list.size();i++){ //String str = (String) list.get(i);//但是在這里會(huì)報(bào)錯(cuò)java.lang.ClassCastException,我們不能直接將Integer類型的數(shù)據(jù)轉(zhuǎn)換成String System.out.println(list.get(i).getClass()); }
如代碼中所示,當(dāng)我們定義了一個(gè)List,list默認(rèn)的類型是所有對(duì)象的基類Object,那么我們?nèi)〕鰯?shù)據(jù)的時(shí)候需要經(jīng)過一次類型轉(zhuǎn)換才能進(jìn)行對(duì)象的實(shí)際類型的相關(guān)操作。因?yàn)長ist中的類型是Object,那么我們先添加了String類型的數(shù)據(jù),然后再添加Integer或者其他類型的數(shù)據(jù)也是允許的,因?yàn)榫幾g時(shí)List中是Object類型的數(shù)據(jù),然而運(yùn)行的時(shí)候卻是它本身的類型,所以當(dāng)我們將List中的數(shù)據(jù)當(dāng)作String處理時(shí)會(huì)拋出java.lang.ClassCastException
。
那么有沒有什么辦法可以使集合能夠記住集合內(nèi)元素各類型,且能夠達(dá)到只要編譯時(shí)不出現(xiàn)問題,運(yùn)行時(shí)就不會(huì)出現(xiàn)java.lang.ClassCastException
異常呢?答案就是使用泛型。
1.2 什么是泛型
Java泛型設(shè)計(jì)原則是:只要在編譯時(shí)期沒有出現(xiàn)警告,那么運(yùn)行時(shí)期就不會(huì)出現(xiàn)ClassCastException異常。
泛型,即“參數(shù)化類型”,把類型明確的工作推遲到創(chuàng)建對(duì)象或調(diào)用方法的時(shí)候才去明確的特殊類型,把<數(shù)據(jù)類型>當(dāng)作是參數(shù)一樣傳遞。
什么是泛型?為什么要使用泛型?
泛型,即“參數(shù)化類型”。一提到參數(shù),最熟悉的就是定義方法時(shí)有形參,然后調(diào)用此方法時(shí)傳遞實(shí)參。那么參數(shù)化類型怎么理解呢?
顧名思義,就是將類型由原來的具體的類型參數(shù)化,類似于方法中的變量參數(shù),此時(shí)類型也定義成參數(shù)形式(可以稱之為類型形參),
然后在使用/調(diào)用時(shí)傳入具體的類型(類型實(shí)參)。
泛型的本質(zhì)是為了參數(shù)化類型(在不創(chuàng)建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說在泛型使用過程中,
操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),這種參數(shù)類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法。
相關(guān)術(shù)語:
- ArrayList中的E稱為類型參數(shù)變量
- ArrayList中的Integer稱為實(shí)際類型參數(shù)
- 整個(gè)稱為ArrayList泛型類型
- 整個(gè)ArrayList稱為參數(shù)化的類型
ParameterizedType
泛型的作用:
代碼更加簡潔【不用強(qiáng)制轉(zhuǎn)換】
程序更加健壯【只要編譯時(shí)期沒有警告,那么運(yùn)行時(shí)就不會(huì)拋出ClassCastException
的異?!?/p>
可讀性和穩(wěn)定性【在編寫集合的時(shí)候,就限定了類型】
List<String> strlist = new ArrayList<String>(); List<Integer> intlist = new ArrayList<Integer>(); strlist.add("A"); strlist.add("B"); //strlist.add(123);//編譯時(shí)報(bào)錯(cuò) for(String str:strlist){ System.out.println(str); //A //B } //加入Java開發(fā)交流君樣:756584822一起吹水聊天 System.out.println(strlist.getClass());//class java.util.ArrayList System.out.println(intlist.getClass());//class java.util.ArrayList System.out.println(strlist.getClass()==intlist.getClass());//true
泛型擦除
泛型是提供給javac編譯器使用的,它用于限定集合的輸入類型,讓編譯器在源代碼級(jí)別上,即擋住向集合中插入非法數(shù)據(jù)。但編譯器編譯完帶有泛型的java程序后生成的class文件中將不再帶有泛型信息,以此使程序的運(yùn)行效率不受到影響,這個(gè)過程稱之為“擦除”。
泛型這個(gè)概念是JDK5提出的,JDK5以前的版本是沒有泛型的,需要建通JDK5以下的集合。當(dāng)把帶有泛型特性的集合賦值給老版本的集合的時(shí)候,會(huì)把泛型給擦除了,它保留的是類型參數(shù)的上限,即Object。而當(dāng)我們將沒有類型參數(shù)的集合賦給帶類型參數(shù)的集合,也不會(huì)報(bào)錯(cuò),僅僅是會(huì)提示“未經(jīng)檢查的轉(zhuǎn)換(Unchecked assignment)”,甚至也可以將它賦給其他不同類型的帶有泛型特性的集合,只是依舊會(huì)拋出ClassCastException異常。
//類型被擦除了,保留的是類型的上限,String的上限就是Object List list = strlist; List<String> stringList2 = list; List<Integer> intList2 = list;//你也可以把它賦給Integer類型的集合,但是當(dāng)你把這個(gè)集合當(dāng)成Integer的集合操作的時(shí)候,依舊會(huì)拋出ClassCastException異常 for (Integer i:intList2){//java.lang.ClassCastException System.out.println(i); }
2、泛型的定義和使用
2.1 泛型類\泛型接口
泛型類、泛型接口就是把泛型定義在類或者接口上,在用戶使用該類的時(shí)候才把類型明確下來。我們常用的集合,List,Map<K,V>,Stack……就是泛型類。在類上定義的泛型,在泛型類的方法、變量中都可以使用。
由于類型參數(shù)變量T在java泛型中僅僅是一個(gè)占位符,在傳遞參數(shù)之后才能使用,即在完成實(shí)例創(chuàng)建之后才能使用,所以在泛型類中,不能定義包含泛型類型的靜態(tài)變量和靜態(tài)方法,會(huì)報(bào)錯(cuò)cannot be referenced from a static context。泛型類中包含泛型類型的變量和方法必須在創(chuàng)建了實(shí)例明確了傳遞的類型參數(shù)后才可以使用。
class Myset<T>{ private T value; //public static T sval;//cannot be referenced from a static context public static int sval2; //加入Java開發(fā)交流君樣:756584822一起吹水聊天 public Myset(){ } public Myset(T val){ this.value = val; } public void setValue(T value) { this.value = value; } public T getValue() { return value; } /* public static T getSval(){//cannot be referenced from a static context return sval; }*/ } Myset<String> myset = new Myset<>(); myset.setValue("12345"); System.out.println(myset.getValue());//12345 myset = new Myset<>("23"); //加入Java開發(fā)交流君樣:756584822一起吹水聊天 System.out.println(myset.getClass());//class liwx.learning.Myset
2.2 泛型方法
public static <T> void PrintArray(T [] arr){ System.out.print("["); for(T t:arr){ System.out.print(t+","); } System.out.println("]"); } Integer[] a = {1,2,3,4,5,6,7}; PrintArray(a);//[1,2,3,4,5,6,7,]
2.3 泛型類的繼承
泛型類的子類有兩種繼承方式
- 子類不明確泛型類的參數(shù)變量,子類也是泛型類
- 子類明確泛型類的參數(shù)變量,子類不是泛型類
//子類不明確泛型類的參數(shù)變量,子類也是泛型類 class MyChiSet1<T> extends Myset<T>{ public MyChiSet1(){ } public MyChiSet1(T val){ super(val); } //加入Java開發(fā)交流君樣:756584822一起吹水聊天 } //子類明確泛型類的參數(shù)變量,子類不是泛型類 class MyChiSet2 extends Myset<String>{ public MyChiSet2(){ } public MyChiSet2(String val){ super(val); } }
2.4 類型通配符?及其上下限
通配符<?>和類型參數(shù)變量的區(qū)別是什么?通配符<?>是實(shí)參而不是類型形參,而且List<?>在邏輯上是List,List等所有List<具體類型實(shí)參>的父類,它的使用比類型形參T更加靈活,但傳入的通配符通常進(jìn)行的是許多于具體類型無關(guān)的操作,如果涉及到具體類型相關(guān)的操作,以及返回值,還是需要使用泛型方法T。
當(dāng)我們使用?號(hào)通配符的時(shí)候,只能調(diào)用與對(duì)象無關(guān)的方法,不能調(diào)用對(duì)象與類型有關(guān)的方法。因?yàn)橹钡酵饨缡褂貌胖谰唧w的類型是什么。
//雖然Object是所有類的基類,但是List<Object>在邏輯上與List<Integer>等并沒有繼承關(guān)系,這個(gè)方法只能傳入List<Object>類型的數(shù)據(jù) public static void showOList(List<Object> list){ System.out.println(list.size()); } //同理,這個(gè)方法只能傳入List<Number>類型的數(shù)據(jù),并不能傳入List<Integer> public static void showList(List<Number> list){ System.out.println(list.size()); }//加入Java開發(fā)交流君樣:756584822一起吹水聊天 //使用通配符,List<?>在邏輯上是所有List<Number>,List<Integer>,List<String>……的父類,可以傳遞所有List類型的數(shù)據(jù),但是不能在List<?>類型的數(shù)據(jù)進(jìn)行于具體類型相關(guān)的操作,如add public static void showList2(List<?> list){ System.out.println("<?>"); System.out.println(list.size()); } //設(shè)置通配符上限,可以傳入List<Number及Number的子類> public static void showNumList(List<? extends Number> list){ System.out.println(list.size()); } //設(shè)置通配符上限,List<? super Number>只可以傳入List<Number及其父類> public static boolean Compare(List<? super Number> list1,List<? super Integer> list2){ return list1.size()>list2.size(); }
List<Integer> Intgetlist = new ArrayList<>(); List<Number> Numberlist = new ArrayList<>(); //雖然Number是Integet的父類,但是傳入List,它們邏輯上沒有了繼承關(guān)系 System.out.println(Intgetlist.getClass()==Numberlist.getClass());//true //加入Java開發(fā)交流君樣:756584822一起吹水聊天 //showList(java.util.List<java.lang.Number>) //List<Integer>和List<Number>邏輯上無繼承關(guān)系,所以無法調(diào)用 //showList(Intgetlist);//showList(java.util.List<java.lang.Number>)in FXtest cannot be applied to(java.util.List<java.lang.Integer>) showList(Numberlist); //public static void showList2(List<?> list) //通配符List<?>在邏輯上是所有List<具體參數(shù)類型>的父類,方法可以傳入其子類類型的數(shù)據(jù) showList2(Intgetlist); showList2(Numberlist); // public static void showNumList(List<? extends Number> list) //當(dāng)設(shè)定了通配符上限,只能傳入List<Number及其子類>的數(shù)據(jù) List<String> Stringlist = new ArrayList<>(); showNumList(Intgetlist); showNumList(Numberlist);//加入Java開發(fā)交流君樣:756584822一起吹水聊天 //showNumList(Stringlist);//showNumList(java.util.List<? extends java.lang.Number>)in FXtest cannot be applied to(java.util.List<java.lang.String>) //public static boolean Compare(List<? super Number> list1,List<? super Integer> list2) //當(dāng)設(shè)定了通配符下限,List<? super Number>只能傳入List<Number及其父類>的數(shù)據(jù),不能傳入子類Integer的List, // 而List<? super Integer>則可以傳入其父類Number的List //Compare(Intgetlist,Numberlist); Compare(Numberlist,Intgetlist);
通配符的使用在邏輯上還原了泛型類傳入數(shù)據(jù)類型的參數(shù)父類子類的繼承關(guān)系,同時(shí)也可以按照需求設(shè)定通配符的上限了下限。
- List<?>在邏輯上是所有List<具體參數(shù)類型>的父類,可對(duì)所有List數(shù)據(jù)進(jìn)行操作
- List<? extends Type>設(shè)定了通配符的上限,可對(duì)所有Type及Type的子類進(jìn)行操作
- List<? super Type>設(shè)定了通配符的下限,可對(duì)所有Type及Type的父類進(jìn)行操作
以上就是Java泛型常見面試題(面試必問)的詳細(xì)內(nèi)容,更多關(guān)于Java泛型面試題的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中的三種標(biāo)準(zhǔn)注解和四種元注解說明
這篇文章主要介紹了Java中的三種標(biāo)準(zhǔn)注解和四種元注解說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02Java實(shí)現(xiàn)的日期處理類完整實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)的日期處理類,結(jié)合完整實(shí)例形式分析了Java針對(duì)日期的獲取、運(yùn)算、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09詳解Spark?Sql在UDF中如何引用外部數(shù)據(jù)
這篇文章主要為大家介紹了詳解Spark?Sql在UDF中如何引用外部數(shù)據(jù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Java網(wǎng)絡(luò)編程TCP實(shí)現(xiàn)文件上傳功能
這篇文章主要為大家詳細(xì)介紹了Java網(wǎng)絡(luò)編程TCP實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07Springboot跨域CORS處理實(shí)現(xiàn)原理
這篇文章主要介紹了Springboot跨域CORS處理實(shí)現(xiàn)原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04springboot Jpa多數(shù)據(jù)源(不同庫)配置過程
這篇文章主要介紹了springboot Jpa多數(shù)據(jù)源(不同庫)配置過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05JAVA使用隨機(jī)數(shù)實(shí)現(xiàn)概率抽獎(jiǎng)
這篇文章主要為大家詳細(xì)介紹了JAVA使用隨機(jī)數(shù)實(shí)現(xiàn)概率抽獎(jiǎng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11