18.Java泛型
1.為什么需要泛型
List list1=new ArrayList(Arrays.asList(new String("string"),new Integer(20))); String str=(String)list.get(0);//強(qiáng)制向下轉(zhuǎn)型System.out.println(str.matches("y\\w+"));
說明:?
- Java在1.5版本的時(shí)候引入了泛型機(jī)制,list1就是沒有使用泛型的容器,可以向容器中添加任何類的對(duì)象
- 傳入容器的對(duì)象都是Object對(duì)象,從容器中取出對(duì)象的時(shí)候可以強(qiáng)制向下轉(zhuǎn)型(很有可能出錯(cuò),只有在運(yùn)行時(shí)才會(huì)報(bào)錯(cuò))
引入泛型的目的:
- 1.限制添加進(jìn)容器的元素的類型
- List<String> list=new ArrayList(); list.add(new String("Hello world")); list.add(new Integer(12));//編譯時(shí)就會(huì)報(bào)錯(cuò) String str=list.get(0);//不需要轉(zhuǎn)型
- 2.代碼復(fù)用(或者說減少冗余的代碼)
2.泛型的寫法
2.1 泛型類
語法:class ClassName<E>{//code}
class Cell<T>{private T t;
public Cell(T t){
this.t=t;
}
}
?
說明:
- 1.泛型并不是一種新的類型,Cell<Integer>和Cell<String>都是Cell類,原始類Cell,Cell<Integer>,Cell<String>是Cell的兩種不同的泛型調(diào)用。?
- 即Cell和Cell<E>是同一個(gè)類,后者稱為有類型參數(shù)的泛型類。
- Cell<String> cell2=new Cell<String>("Hello"); System.out.println(cell1.getClass()==cell2.getClass());//true,Class對(duì)象相同所以是同一種類型
- 2.靜態(tài)函數(shù)、靜態(tài)成員不能使用類的類型參數(shù)、靜態(tài)內(nèi)部類不能使用外部類的類型參數(shù)
- 理由是靜態(tài)函數(shù)只能使用靜態(tài)屬性,靜態(tài)函數(shù)屬性和內(nèi)部類不依賴于類的對(duì)象,也就是說使用靜態(tài)函數(shù)的時(shí)候,參數(shù)化類型還沒有被初始化,因此不能使用。
- 3.泛型的類型參數(shù)不存在繼承關(guān)系前后必須一致
- List<Number> list=new ArrayList<Integer>();//wrong List<Integer> list=new ArrayList<Integer>();//right
2.2 泛型函數(shù)
語法:public<E> E f(E e){//code}
public class Main {public static void main(String[] atgs) {System.out.println(new MyClass().f(20));//調(diào)用泛型函數(shù)} }class MyClass{public<E> E f(E e){return e;} }問題:怎么使用
- 方式1不指定泛型:Serializable?b=Main.dosomthing(new String(),new Integer(1));//最小公共父類是?Serializable
- 方式2指定泛型:Number?a=Main.<Number>dosomthing(1,2.2);//指定參數(shù)化類型為Number
- 例子:
- public static void main(String[] args) { /**不指定泛型的時(shí)候*/ int i=Test2.add(1, 2); //這兩個(gè)參數(shù)都是Integer,所以T為Integer類型 Number f=Test2.add(1, 1.2);//這兩個(gè)參數(shù)一個(gè)是Integer,以風(fēng)格是Float,所以取同一父類的最小級(jí),為Number Object o=Test2.add(1, "asd");//這兩個(gè)參數(shù)一個(gè)是Integer,以風(fēng)格是Float,所以取同一父類的最小級(jí),為Object /**指定泛型的時(shí)候*/ int a=Test2.<Integer>add(1, 2);//指定了Integer,所以只能為Integer類型或者其子類 int b=Test2.<Integer>add(1, 2.2);//編譯錯(cuò)誤,指定了Integer,不能為Float Number c=Test2.<Number>add(1, 2.2); //指定為Number,所以可以為Integer和Float } //這是一個(gè)簡(jiǎn)單的泛型方法 public static <T> T add(T x,T y){ return y;
}
3.泛型進(jìn)階
3.1有界的類型參數(shù)—對(duì)類型參數(shù)的類型進(jìn)行限制
- List<T> T沒有限制
- List<T extends Number> Number是T的上界,傳入容器中的類必須是Number或者Number的子類
- List<T extends Integer & Comparable<T>> 可以有多個(gè)上界,但是上界里只能有一個(gè)類,可以有多個(gè)接口,而且它們中的函數(shù)都可以使用。
- 備注:List<Number> List<Integer>是沒有繼承關(guān)系的
3.2 參數(shù)化類型的數(shù)組
- 1.泛型和數(shù)組之間有一個(gè)矛盾,數(shù)組必須知道具體的類型,但是泛型的參數(shù)化類型恰恰確定不了具體的類型,不能直接創(chuàng)建泛型數(shù)組
- E[] arrays=new E[10];//wrong
- E[] arrays=null;//right
- 2.不能實(shí)例化帶有參數(shù)化類型的數(shù)據(jù)
- ArrayList<String>[] arrays=new ArrayList<String>[10];//wrong
- ArrayList<String>[] arrays=new ArrayList[10];?//right----arrays[0]中只能存放String類型的對(duì)象
3.3 通配符
1.問題:
- List<Number> 與 List<Integer>之間沒有任何繼承關(guān)系,所以sum函數(shù)不能接受List<Integer>作為參數(shù)
?解決:
- 使用通配符?可疑解決
- public static double sum(List<? extends Number> arrays){int sum=0;for(Number integer:arrays){sum+=integer.doubleValue();}return sum;}?
2.通配符的界限----有點(diǎn)難理解
- 通配符與類型參數(shù)并不相同,只能有一個(gè)邊界,也就是說不能這么寫,List<? extends Number &Comparable>//wrong
- a.無界通配符List<?> ------不能存入元素除了null
- 取出來的元素是Object,List<?> 相當(dāng)于 List<? extends Object>不是List<Object>
- 不能像其中添加任何的對(duì)象
- b.有上屆的通配符List<? extends Number> 取出來的元素是Number----不能存入元素除了null
- List<??extends Animal>的子類是List<Animal> List<Bird> List<Cat>
- 但是傳入容器中的參數(shù)類型是不定的(假設(shè)可以),可能是Animal,Bird,Cat
- 傳入的參數(shù)是未知的,還會(huì)有沖突,所以java為了保護(hù)其類型一致,禁止向這種容器中添加除了null之外的所有類型數(shù)據(jù)
- c.有下界的通配符List<? Super Number> ?取出來的元素是Object-----可以存入Number以及子類對(duì)象,但是不能存入Number的父類
- List<? super Animal> 是 List<? super Bird>的子類型
- List<? super Animal> 只能添加Animal及其子類,原因是Animal的子類可以向上轉(zhuǎn)型為Animal類的對(duì)象,但是Animal的父類的類型不確定,所以不能添加到容器中。
總結(jié):
- 通配符修飾的容器,向其中添加元素的限制很多,比如List<?>,List<? extends Number>都不能向里面添加元素非null元素,List<? super Number>只能添加Number以及它的子類。
- 一般用作函數(shù)的參數(shù) ?
- public void f(List<?>){//code} ?
- public void f(List<? extends Number>){//code} ? 傳入List<Number>、List<Integer>都是可以的
- public void f(List<? super Integer>){//code} ?傳入List<? super Number>可以的,List<? super Number>是List<? super Integer>的子類(子集)
- 例子:
- public static void main(String[] atgs) {List<? super Number> list=new ArrayList<>();list.add(new Integer(1));list.add(new Double(3.9));System.out.println(sum(list));
}public static double sum(List<? super Integer> arrays){double sum=0;for(Object integer:arrays){Number number=(Number) integer;sum+=number.doubleValue();}return sum;}
備注:List<? super Number> 是 List<? super Integer>的子類,所以可以執(zhí)行
4.泛型擦除
4.1泛型擦除概述
Java的泛型是偽泛型。為什么說Java的泛型是偽泛型呢?因?yàn)?#xff0c;在編譯期間,所有的泛型信息都會(huì)被擦除掉。正確理解泛型概念的首要前提是理解類型擦除(type erasure)。 Java中的泛型基本上都是在編譯器這個(gè)層次來實(shí)現(xiàn)的。在生成的Java字節(jié)碼中是不包含泛型中的類型信息的。使用泛型的時(shí)候加上的類型參數(shù),會(huì)在編譯器在編譯的時(shí)候去掉。這個(gè)過程就稱為類型擦除。
4.2 類型擦除的表現(xiàn)
- 1.List<Integer>和List<Number>的類型相同
- 原因就是編譯的時(shí)候,由于類型擦除,均變成了List<Object>,這里給了我們一個(gè)信息,那么運(yùn)行的時(shí)候是不是就可以向容器中添加任意類型的數(shù)據(jù)了,因?yàn)樗麄兌际荗bject的子類.
- 2.運(yùn)行時(shí)向容器中添加任意元素
- public static void main(String[] atgs) {List<String> list=new ArrayList<>();//list.add(new Integer(1));編譯時(shí)添加wrongtry {Method add=list.getClass().getMethod("add",Object.class);add.invoke(list,new Integer(1));//運(yùn)行時(shí)添加可以add.invoke(list,new String("yangyun"));add.invoke(list,new Double(1.23));add.invoke(list,new Float(1.1234));} catch (Exception e) {e.printStackTrace();}System.out.println(list);}
- ?利用反射在運(yùn)行的時(shí)候,可以向list中添加任何元素
4.3 泛型的寫法
- a.泛型的寫法
- 1.ArrayList list=new ArrayList<Integer>();------這里的參數(shù)化類型沒有作用(有什么作用?)
- 2.ArrayList<Integer> list=new ArrayList<>();---類型參數(shù)有作用
- 3.new ArrayList<String>().add("yangyun");-----類型參數(shù)有作用
- 真正涉及類型檢查的是它的引用,因?yàn)槲覀兪鞘褂盟?list 來調(diào)用它的方法,比如說調(diào)用add()方法。所以arrayList1引用能完成泛型類型的檢查
4.4 運(yùn)行時(shí)取值-自動(dòng)類型轉(zhuǎn)換
- List<String>編譯期擦除之后變?yōu)樵碱愋?List<Object>,那么取值的時(shí)候的類型自動(dòng)轉(zhuǎn)換是怎么做的?
- 通過參考文獻(xiàn)的3.2可以知道,轉(zhuǎn)型是在調(diào)用get函數(shù)的地方進(jìn)行的。
4.5 泛型與函數(shù)覆蓋
?
class Father<E>{private E e;public void setE(E e) {this.e = e;}public E getE() {return e;} }class Son extends Father<String>{@Overridepublic void setE(String string){super.setE(string);}@Overridepublic String getE(){return super.getE();} }?
說明上邊的繼承關(guān)系中,Father是一個(gè)泛型類,Son繼承自Father<String>,并覆蓋了父類中的函數(shù)。
問題:覆蓋應(yīng)該是參數(shù)一樣的,父類中的函數(shù)的參數(shù)編譯后擦除應(yīng)該是Object,子類的中的函數(shù)參數(shù)是String,為什么構(gòu)成覆蓋?
解決:J于是JVM采用了一個(gè)特殊的方法,來完成這項(xiàng)功能,那就是橋方法。
橋方法:就是子類中,參數(shù)為Object的函數(shù),我們自己寫的函數(shù)回去調(diào)用橋方法,間接的實(shí)現(xiàn)重載
說明:參考文獻(xiàn)2中有具體的例子?
?
5.泛型知識(shí)點(diǎn)補(bǔ)充
5.1 泛型與內(nèi)部類
內(nèi)部類可以使用外部類的一切數(shù)據(jù),包括類型參數(shù),反過來外部類不可以使用內(nèi)部類的類型參數(shù),但是外部類可以利用內(nèi)部類的對(duì)象使用內(nèi)部類的private屬性(在外部類的范圍內(nèi))
5.2 使用泛型類的時(shí)候應(yīng)該指出具體的類型參數(shù)的類型
5.3 類型
System.out.println(list instanceof ArrayList<?>);//可以,但是通配符不能設(shè)置界限 System.out.println(list instanceof ArrayList<String>);//不行?
5.4 利用反射創(chuàng)建泛型數(shù)組
說明:我們知道參數(shù)類型數(shù)組不能直接實(shí)例化,那么我們?nèi)绾畏祷胤盒蛿?shù)組,答案就是利用反射
public class Main<E>{public static void main(String[] atgs) {Main<String> obj=new Main<>();String[] arrays=obj.getArrays(2);Array.set(arrays,0,"123");Array.set(arrays,1,"yangyun");Array.set(arrays,2,"Hello");System.out.println(Arrays.toString(arrays));}public E[] getArrays(int length) {return (E[])Array.newInstance(String.class,3);} }?
參考文獻(xiàn)
http://blog.csdn.net/baple/article/details/25056169
https://my.oschina.net/fuyong/blog/719013
http://www.cnblogs.com/penghongwei/p/3300094.html ? //特別好的Java反射資料
?
轉(zhuǎn)載于:https://www.cnblogs.com/yangyunnb/p/6139861.html
與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
- 上一篇: XidianOJ 1037 倍流畅序列
- 下一篇: Java ArrayList和Vec