摘要:對每個泛型類只生成唯一的一份目標代碼該泛型類的所有實例都映射到這份目標代碼上,在需要的時候執行類型檢查和類型轉換。參考文章的模板是典型的實現,而泛型則是實現,將多種泛型類形實例映射到唯一的字節碼表示是通過類型擦除實現的。
一 前言:初識泛型
廢話不說,先來看一段代碼:
public class Holder { private Object data; public Holder(Object data ){ this.data = data; } public void setData(Object data) { this.data = data; } public Object getData() { return data; } public static void main(String[] args){ Holder holder = new Holder(new SomeNode()); SomeNode someNode = holder.getData(); } } class SomeNode{}
Holder類是一個容器,它的作用是用來保存其他類的,這里我們用它來保存SomeNode類,隨后把它取出來,編譯運行,結果如下:
Error:(21, 43) java: incompatible types required: SomeNode found: java.lang.Object
意思是,需要的是SomeNode,取出來的卻是Object,如此看來,如果我想保存SomeNode類,就只能把data聲明為SomeNode:
private SomeNode data;
這就意味著我們需要為每一個類創造一個Holder,這肯定是不行的,于是泛型的作用來了,泛型,可以理解為任何類型,意思是我可以聲明一個可以容納任何類型的容器:
public class Holder{ private T data; public Holder(T data ){ this.data = data; } public void setData(T data) { this.data = data; } public T getData() { return data; } public static void main(String[] args){ Holder holder = new Holder (new SomeNode()); SomeNode someNode = holder.getData(); } } class SomeNode{}
注意寫法,在類聲明后面加個就行了,你也可以加,只是一個占位符,形參而已。然后我們再把它取出來:
Process finished with exit code 0
程序沒有報錯,如果這時候我們使用holder的set()方法去插入設置一些非SomeNode類型的值,代碼如下:
public static void main(String[] args){ Holderholder = new Holder (new SomeNode()); SomeNode someNode = holder.getData(); holder.setData("AAAA"); }
看結果:
Error:(22, 15) java: method setData in class Holdercannot be applied to given types; required: SomeNode found: java.lang.String reason: actual argument java.lang.String cannot be converted to SomeNode by method invocation conversion
泛型機制就自動為我們報錯,很方便。
二 泛型類:元組(Tuple),返回多個對象熟悉python的同學都知道元組的概念,它是一個只讀列表,在返回多個結果時是很有用的,我們利用泛型特性來創造一個包含兩個對象的元組:
public class Tuple { public static void main(String[] args){ TwoTuplet = new TwoTuple ("Monkey",12); System.out.println(t.toString()); } } class TwoTuple{ final A first; final B second; public TwoTuple(A a,B b){ first = a; second = b; } public String toString(){ return "("+first+","+second+")"; } }
來看結果:
(Monkey,12)
是不是很方便:)如果想要一個長度為3的元組可以這么寫:
public class Tuple { public static void main(String[] args){ ThreeTuplet = new ThreeTuple ("Dog",12,true); System.out.println(t.toString()); } } class TwoTuple{ final A first; final B second; public TwoTuple(A a,B b){ first = a; second = b; } public String toString(){ return "("+first+","+second+")"; } } class ThreeTuple extends TwoTuple{ final C three; public ThreeTuple(A a,B b,C c){ super(a,b); three = c; } public String toString(){ return "("+first+","+second+","+three+")"; } }
結果如下:
(Dog,12,true)三 泛型接口
泛型接口的定義和泛型類的定義類似,我們來定義一個生成器接口:
public interface Generator{ T next(); }
接著我們實現這個接口,來生成斐波拉契數:
public class Fib implements Generator四 泛型方法{ private int count = 0; @Override public Integer next() { return fib(count++); } private int fib(int n){ if (n<2) return 1; else return fib(n-2) + fib(n-1); } public static void main(String[] args){ Fib f = new Fib(); for (int i=0;i<100;i++){ System.out.println(f.next()); } } }
比起泛型類,我們更推薦去使用泛型方法,泛型方法定義起來也很簡單,我們只需將泛型參數放在返回類型前面即可:
public class GenericMethods { publicvoid f(T x){ System.out.println(x.getClass().getName()); } public static void main(String[] args){ GenericMethods g = new GenericMethods(); g.f("Hello"); g.f(100); g.f(true); } }
這里我們定義了一個泛型方法f(),并使用getClass獲取類的相關信息(關于Class對象的知識點這里),來看結果:
java.lang.String java.lang.Integer java.lang.Boolean
這里還要注意一下Varargs(變長參數)機制和泛型的結合:
public class GenericVarargs { public staticList makeList(T...args){ List list = new ArrayList (); for (T item : args){ list.add(item); } return list; } public static void main(String[] args){ List list = makeList("A","B","C","D"); System.out.println(list); } }
結果如下:
[A, B, C, D]六 類型擦除
在認識類型擦除之前,我們首先要明白編譯器對泛型的處理有兩種方式:
1.Code specialization
在實例化一個泛型類或者泛型方法是都生成一份新的字節碼,比如對于List
2.Code sharing
對每個泛型類只生成唯一的一份目標代碼;該泛型類的所有實例都映射到這份目標代碼上,在需要的時候執行類型檢查和類型轉換。參考文章
C++的模板是典型的Code specialization實現,而Java泛型則是Code sharing實現,將多種泛型類形實例映射到唯一的字節碼表示是通過類型擦除(type erasue)實現的。對擦除更通俗的理解就是:編譯器生成的bytecode是不包涵泛型信息的。我們看下面的代碼:
public class ErasedType { public static void main(String[] args){ Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList ().getClass(); System.out.println(c1 == c2); } }
結果如下:
true
也就是說我們在實例化ArrayList
public class ErasedType { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Listlist = new ArrayList (); list.add("ABC"); list.getClass().getMethod("add",Object.class).invoke(list,123); System.out.println(list); } }
看結果:
[ABC, 123]
我們很順利的把Integer型的123插入到了String的List里:)
七 后記由于類型擦除的存在,我們往往會在使用泛型特性的時候遇到一些詭異的問題,由于篇幅原因,這里不展開了:)我將在另外一篇文章中集中的總結一下這方面的問題。
我的微信號是aristark,歡迎交流指正!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/65687.html
摘要:總結數組與泛型的關系還是有點復雜的,中不允許直接創建泛型數組。本文分析了其中原因并且總結了一些創建泛型數組的方式。 簡介 上一篇文章介紹了泛型的基本用法以及類型擦除的問題,現在來看看泛型和數組的關系。數組相比于Java 類庫中的容器類是比較特殊的,主要體現在三個方面: 數組創建后大小便固定,但效率更高 數組能追蹤它內部保存的元素的具體類型,插入的元素類型會在編譯期得到檢查 數組可以持...
摘要:操作對應字節碼中的個字節我們可以看到最關鍵的操作其實就是調用的其實是類的方法,此方法的入參類型是,返回值類型是,翻譯過來就是類的方法,執行完后將獲得的結果做了,檢查返回的對象類型是否是。 語法糖(Syntactic Sugar)的出現是為了降低我們編寫某些代碼時陷入的重復或繁瑣,這使得我們使用語法糖后可以寫出簡明而優雅的代碼。在Java中不加工的語法糖代碼運行時可不會被虛擬機接受,因此...
摘要:然而中的泛型使用了類型擦除,所以只是偽泛型??偨Y本文介紹了泛型的使用,以及類型擦除相關的問題。一般情況下泛型的使用比較簡單,但是某些情況下,尤其是自己編寫使用泛型的類或者方法時要注意類型擦除的問題。 簡介 Java 在 1.5 引入了泛型機制,泛型本質是參數化類型,也就是說變量的類型是一個參數,在使用時再指定為具體類型。泛型可以用于類、接口、方法,通過使用泛型可以使代碼更簡單、安全。然...
閱讀 2109·2021-11-23 09:51
閱讀 2847·2021-11-22 15:35
閱讀 2947·2019-08-30 15:53
閱讀 1047·2019-08-30 14:04
閱讀 3285·2019-08-29 12:39
閱讀 1817·2019-08-28 17:57
閱讀 1106·2019-08-26 13:39
閱讀 560·2019-08-26 13:34