废话不说,先来看一段代码: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,欢迎交流指正!