转载

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类,随后把它取出来,编译运行,结果如下:

Error:(21, 43) java: incompatible types   required: SomeNode   found:    java.lang.Object

意思是,需要的是SomeNode,取出来的却是Object,如此看来,如果我想保存SomeNode类,就只能把data声明为SomeNode:

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{}

注意写法,在类声明后面加个就行了,你也可以加,只是一个占位符,形参而已。然后我们再把它取出来:

 Process finished with exit code 0

程序没有报错,如果这时候我们使用holder的set()方法去插入设置一些非SomeNode类型的值,代码如下:

 public static void main(String[] args){         Holder<SomeNode> holder = new Holder<SomeNode>(new SomeNode());         SomeNode someNode = holder.getData();         holder.setData("AAAA");      }

看结果:

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

泛型机制就自动为我们报错,很方便。

二 泛型类:元组(Tuple),返回多个对象

熟悉python的同学都知道元组的概念,它是一个只读列表,在返回多个结果时是很有用的,我们利用泛型特性来创造一个包含两个对象的元组:

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+")";     } }

来看结果:

(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,欢迎交流指正!

原文  https://segmentfault.com/a/1190000004714370
正文到此结束
Loading...