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