廢話不說,先來看一段代碼:java
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類,隨後把它取出來,編譯運行,結果以下:python
Error:(21, 43) java: incompatible types required: SomeNode found: java.lang.Object
意思是,須要的是SomeNode,取出來的倒是Object,如此看來,若是我想保存SomeNode類,就只能把data聲明爲SomeNode:segmentfault
private SomeNode data;
這就意味着咱們須要爲每個類創造一個Holder,這確定是不行的,因而泛型的做用來了,泛型,能夠理解爲任何類型,意思是我能夠聲明一個能夠容納任何類型的容器:微信
public class Holder<T> { 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<SomeNode> holder = new Holder<SomeNode>(new SomeNode()); SomeNode someNode = holder.getData(); } } class SomeNode{}
注意寫法,在類聲明後面加個就好了,你也能夠加,只是一個佔位符,形參而已。而後咱們再把它取出來:app
Process finished with exit code 0
程序沒有報錯,若是這時候咱們使用holder的set()方法去插入設置一些非SomeNode類型的值,代碼以下:ide
public static void main(String[] args){ Holder<SomeNode> holder = new Holder<SomeNode>(new SomeNode()); SomeNode someNode = holder.getData(); holder.setData("AAAA"); }
看結果:ui
Error:(22, 15) java: method setData in class Holder<T> cannot 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
泛型機制就自動爲咱們報錯,很方便。this
熟悉python的同窗都知道元組的概念,它是一個只讀列表,在返回多個結果時是頗有用的,咱們利用泛型特性來創造一個包含兩個對象的元組:.net
public class Tuple { public static void main(String[] args){ TwoTuple<String,Integer> t = new TwoTuple<String, Integer>("Monkey",12); System.out.println(t.toString()); } } class TwoTuple<A,B>{ final A first; final B second; public TwoTuple(A a,B b){ first = a; second = b; } public String toString(){ return "("+first+","+second+")"; } }
來看結果:code
(Monkey,12)
是否是很方便:)若是想要一個長度爲3的元組能夠這麼寫:
public class Tuple { public static void main(String[] args){ ThreeTuple<String,Integer,Boolean> t = new ThreeTuple<String, Integer, Boolean>("Dog",12,true); System.out.println(t.toString()); } } class TwoTuple<A,B>{ final A first; final B second; public TwoTuple(A a,B b){ first = a; second = b; } public String toString(){ return "("+first+","+second+")"; } } class ThreeTuple<A,B,C> extends TwoTuple<A,B>{ 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> { T next(); }
接着咱們實現這個接口,來生成斐波拉契數:
public class Fib implements Generator<Integer> { 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 { public <T> void 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 static <T> List<T> makeList(T...args){ List<T> list = new ArrayList<T>(); for (T item : args){ list.add(item); } return list; } public static void main(String[] args){ List<String> list = makeList("A","B","C","D"); System.out.println(list); } }
結果以下:
[A, B, C, D]
在認識類型擦除以前,咱們首先要明白編譯器對泛型的處理有兩種方式:
1.Code specialization
在實例化一個泛型類或者泛型方法是都生成一份新的字節碼,好比對於List<String>,List<Integer>,List<Float>產生三份不一樣的字節碼。
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<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } }
結果以下:
true
也就是說咱們在實例化ArrayList<String>和實例化ArrayList<Integer>時是共享一份目標代碼的,泛型類類型信息在編譯的過程當中被擦除了。對於JVM來講,它只看到一份ArrayList(原始類型)而已。咱們還能夠從反射的角度來理解類型擦除:
public class ErasedType { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { List<String> list = new ArrayList<String>(); list.add("ABC"); list.getClass().getMethod("add",Object.class).invoke(list,123); System.out.println(list); } }
看結果:
[ABC, 123]
咱們很順利的把Integer型的123插入到了String的List裏:)
因爲類型擦除的存在,咱們每每會在使用泛型特性的時候遇到一些詭異的問題,因爲篇幅緣由,這裏不展開了:)我將在另一篇文章中集中的總結一下這方面的問題。
個人微信號是aristark,歡迎交流指正!