1 什么是泛型? 泛型,即 “参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用 / 调用时传入具体的类型(类型实参)。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型) 。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法 。
2 为什么要用泛型? 创建一个 List,不指定类型的话,则可以添加任意类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  class  GenericsDemo1  {    public  static  void  main (String[] args)  {         List  arrayList  =  new  ArrayList ();         arrayList.add("string" );         arrayList.add(1 );         for  (int  i  =  0 ; i < arrayList.size(); i++) {             System.out.println(arrayList.get(i));         }     } } string 1 
但是如果如果需要以 String 的类型来处理 arrayList 内的元素,则编译的时候会遇到错误 (编译器不报错):
1 2 3 4 5 6 7 8 9 10 11 12 public  class  GenericsDemo1  {    public  static  void  main (String[] args)  {         List  arrayList  =  new  ArrayList ();         arrayList.add("string" );         arrayList.add(1 );         for  (int  i  =  0 ; i < arrayList.size(); i++) {             String  s  =  (String) arrayList.get(i);         }     } } 
ERROR:
1 Exception in thread "main"  java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 
若是我们在初始化 arraryList 的时候,指定了 String 类型,则编译器能够提前发现问题:
1 2 3 List<String> arrayList = new  ArrayList (); arrayList.add("string" ); 
3 泛型的类型擦除 下面例子中,定义了 List<String> 和 List<Integer>,本意上是 2 个类型,但是在编译后都变成 ArrayList。Java 的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息,使用泛型的时候加上类型参数,在编译器编译时会去掉,这个过程称为类型擦除。
1 2 3 4 5 6 7 8 9 10 11 List<String> stringList = new  ArrayList <>(); stringList.add("string" ); List<Integer> integerList = new  ArrayList <>(); integerList.add(1 ); Class  classStringList  =  stringList.getClass();Class  classIntegerList  =  integerList.getClass();System.out.println(classStringList);  System.out.println(classIntegerList);  System.out.println(classStringList == classIntegerList);   
后面详细讲解这块。
4 泛型的使用 泛型的使用情形分为三种:泛型类、泛型接口、泛型方法
泛型类 泛型类用于类的定义中,可以通过这个方式来实现对类的” 参数化 “,顾名思义,在 new 类的时候可以输入类型参数。典型的用法就是各种容器类,如:List、Set、Map
基本用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  GenericClass <T> {    public  T key;     public  GenericClass (T key) {         this .key = key;     }     public  T getKey ()  {         return  key;     } } public  class  GenericDemo  {    public  static  void  main (String[] args)  {         GenericClass<String> genericClass = new  GenericClass <>("String" );         System.out.println(genericClass.getKey());       } } 
new 泛型类的时候,不一定要传入类型实参。如上的例子所示,使用泛型的时候,若传入泛型类型实参 (String),则创建的类会根据类型参数做相应的限制。如果不传入类型参数的话,如下例子所示,使用泛型类的时候,成员变量可以为任意的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  class  GenericDemo  {    public  static  void  main (String[] args)  {         GenericClass<String> genericClass = new  GenericClass <>("String" );         System.out.println(genericClass.getKey());         GenericClass  genericStr  =  new  GenericClass ("str" );         GenericClass  genericInt  =  new  GenericClass (1 );         GenericClass  genericChar  =  new  GenericClass ('c' );         System.out.println(genericStr.getKey());          System.out.println(genericInt.getKey());          System.out.println(genericChar.getKey());      } } 
需要注意的是:
泛型类的类型参数必须为引用类型 (类类型),不能是基础类型 (string, int …);
不能对泛型类使用 instanceof
1 2 3 4 Integer  val  =  1 ;if  (val instanceof  GenericClass<Integer>) {    System.out.println("test" ); } 
1 2 GenericDemo.java:17 : error: illegal generic type for  instanceof          if  (val instanceof  GenericClass<Integer>)  { 
 
泛型接口 泛型接口的定义和泛型类基本相同,基本用法:
1 2 3 public  interface  GenericsInterface <T> {    public  T getKey () ; } 
未传入类型参数,实现泛型接口类: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  GenericsImpl <T> implements  GenericsInterface <T> {    private  T key;     public  GenericsImpl (T key)  {         this .key = key;     }     @Override      public  T getKey ()  {         return  key;     }     public  static  void  main (String[] args)  {         GenericsImpl<String> generics = new  GenericsImpl ("str" );         System.out.println(generics.getKey());     } } 
注意,其中实现类的声明中,不能忽略泛型类的声明 <T>, 即:public class GenericsImpl<T> implements GenericsInterface<T> {若是忽略了 <T>,编译会出现错误:
1 2 3 4 5 6 7 8 9 10 11 public  class  GenericsImpl  implements  GenericsInterface <T> {	... ...  } ========================================= ERROR: GenericsImpl.java:3 : error: cannot find symbol public  class  GenericsImpl  implements  GenericsInterface <T> {                                                       ^   symbol: class  T  
传入类型参数,实现泛型接口类: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  class  GenericsImplPara  implements  GenericsInterface <String>{    private  String key;     public  GenericsImplPara (String key)  {         this .key = key;     }     @Override      public  String getKey ()  {         return  key;     }     public  static  void  main (String[] args)  {         GenericsImplPara  genericsImplPara  =  new  GenericsImplPara ("test" );         System.out.println(genericsImplPara.getKey());      } } 
当传入类型参数 String 时,则泛型类的声明 <T> 可以忽略。即 public class GenericsImplPara<T> implements GenericsInterface<String> 简化为以上的 public class GenericsImplPara implements GenericsInterface<String>.
泛型方法 与泛型类 在实例化类的时候指明泛型的具体类型不同的是,泛型方法 是在调用方法的时候指明泛型的具体类型。
##### 泛型方法的基本用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  <T> T genericsMethod (GenericClass<T> genericClass)  {    T  instance  =  genericClass.newInstance();     return  instance; } 
类中使用泛型方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public  class  GenericsFruit  {    static  class  Fruit  {         @Override          public  String toString ()  {             return  "Fruit" ;         }     }     static  class  Apple  extends  Fruit  {         @Override          public  String toString ()  {             return  "Apple" ;         }     }     static  class  Person  {         @Override          public  String toString ()  {             return  "Person" ;         }     }     static  class  GenerateTest <T> {                  public  void  show1 (T t)  {             System.out.println(t.toString());         } 				       	         public  <T> void  show2 (T t)  {             System.out.println(t.toString());         } 				       	         public  <E> void  show3 (E t)  {             System.out.println(t.toString());         }       	       	     }     public  static  void  main (String[] args)  {         Apple  apple  =  new  Apple ();         Person  person  =  new  Person ();         GenerateTest<Fruit> generateTest = new  GenerateTest <>(); 				       	         generateTest.show1(apple);       	         generateTest.show2(apple);         generateTest.show2(person);       	         generateTest.show3(apple);         generateTest.show3(person);     }         Apple 	Apple   Person   Apple   Person 
可变参数的泛型方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16       public  <T> void  print (T... args)  {           for (T t: args) {               System.out.println("T is: "  + t);           }       } 		... ...          		generateTest.print("111" ,222 ,"aaaa" ,"2323.4" ,55.55 ); T is: 111  T is: 222  T is: aaaa T is: 2323.4  T is: 55.55  
静态方法使用泛型参数: 如果想要静态方法中使用泛型,即泛型参数。则这个静态方法必须为泛型方法 (<T>):
错误示例:
1 2 3 4 5 6 7         public  static  void  testStatic (T t)  {             System.out.println(t);         } non-static  type variable T cannot be referenced from a static  context         public  static  void  testStatic (T t)  { 
正确用法:
1 2 3 public  static  <T> void  testStatic (T t)  {    System.out.println(t); } 
因为如果是类中的变量使用了静态变量,静态变量的调用是可以在类初始化之前就调用的,而泛型类在初始化之前,还不能确定类型,所以不能在类中使用静态的泛型参数。
泛型方法小结: 
静态方法想要使用泛型参数,则必须使用泛型方法; 
泛型方法必须要有泛型声明 <T>; 
 尽量使用泛型方法 
 
5 泛型通配符 常用的泛型通配符为: T,E,K,V,? ,本质上这些字符都区别,是我们代码中一种约定俗成的东西。如上文提到的 T,我们可以使用 A—Z 中的任意字符替换,并不会影响程序本身的运行,但是如果使用其他字母替换的话,代码整体的可读性较弱,没有统一的规范。
通常:
? 表示不确定的 java 类型;T (type) 表示具体的一个 java 类型;K V (key value) 分别代表 java 键值中的 Key Value;E (element) 代表 Element 
无界通配符 <?> 一般用于不确定或者不关心实际要操作的类类型,表示可以传入任意类类型参数;
1 2 3 public  static  void  testQuestion (GenericClass<?> objs) {    System.out.println(objs); } 
上界通配符 <? extends T> 用 extends 关键字声明,表示参数类型可能是所指定 T 的类型,或者是此 T 类型的子类。这样有两个好处:
如果传入的类型不是 T 或者 T 的子类,编译不成功 
泛型中可以使用 T 的方法,要强转成 T 才能使用  
 
1 2 3 4 5 6 public  <K extends  Apple , E extends  Person > E testUp (K k, E e) {    E  res  =  e;     res.toString(k);          return  res; } 
注 : 类型参数列表中如果有多个类型参数上限,用逗号分开
下界通配符 <? super T> 用 super 关键字声明,表示参数类型可能是所指定的 T 类型,或者是此 T 类型的父类型,直至 Object 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public  class  Animal  {    public  void  eat ()  {         System.out.println("animal eat" );     } } public  class  Dog  extends  Animal  {    public  void  run ()  {         System.out.println("Dog run" );     }     @Override      public  void  eat ()  {         System.out.println("Dog eat" );     } } public  class  GenericLowBound  {    public  static  void  main (String[] args)  {         List<Dog> dogs = new  ArrayList <>();         List<Animal> animals = new  ArrayList <>();         new  GenericLowBound ().test(animals, dogs);     }     private  <T> void  test (List<? super  T> dst, List<T> src)  {         for  (T t: src) {             dst.add(t);         }     } } 
上述例子中 test 方法中 dst 类型范围是 “大于等于” src 类型,所以 dst 的容器也能装下 src。
<?> 和 >T> 的区别1 2 3 4 5 List<T> tList = new  ArrayList <>(); List<?> qList = new  ArrayList <>(); 
在泛型的使用中,?和 T 都是表示某一类型,区别是我们可以对 T 类型的参数进行一些操作(方法调用等),而?却不行,很容理解,因为?是不确定的类型,如果需要对参数进行某个方法调用,我们无法确定?的参数是否有这个方法。
1 2 3 4 5      T  t  =  operate();    ? q = operate(); 
T 是一个确定的 类型,通常用于泛型类和泛型方法的定义,?是一个不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
T 可以用来保证泛型参数的一致性像下面的代码中,约定的 T 是 Number 的子类才可以,但是申明时是用的 Animal 和 Dog,所以就会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24     public  static  void  main (String[] args)  {         List<Dog> dogs = new  ArrayList <>();         List<Animal> animals = new  ArrayList <>();         new  GenericLowBound ().test2(animals, dogs);     }     private  <T extends  Number > void  test2 (List<T> src, List<T> dest)  {         System.out.println(src);         System.out.println(dest);     } Error: ======================== error: method test2 in class  GenericLowBound  cannot be applied to given types;         new  GenericLowBound ().test2(animals, dogs);                              ^   required: List<T>,List<T>   found: List<Animal>,List<Dog>   reason: inference variable T has incompatible bounds     equality constraints: Dog,Animal     upper bounds: Number   where T is a type-variable:     T extends  Number  declared in method <T>test2(List<T>,List<T>) 
类型参数 T 可以多重限定而通配符 ? 不行 使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子类型,此时变量 t 就具有了所有限定的方法和属性。对于通配符 ? 来说,因为它不是一个确定的类型,所以不能进行多重限定。
1 2 3 4 5 6 7 8 9 10 11 public  interface  MultiLimitInterfaceA  {} public  interface  MultiLimitInterfaceB  {} public  class  MultiLimitClass  implements  MultiLimitInterfaceA , MultiLimitInterfaceB{    public  <T extends  MultiLimitInterfaceA  & MultiLimitInterfaceB> void  test (T t)  {         System.out.println(t);     } } 
##### 通配符 ? 可以使用超类限定 而类型参数 T 不行
类型参数 T 只具有 一种类型限定方式 (extends):
但是通配符 ? 可以进行两种限定 (extends  + super):
List<T>,List<Object>,List<?> 区别前面已经说过 <T> 和 <?> 的区别,这里主要说明下 <Object>,Object 和 T 不同点在于,Object 是一个实打实的类,并没有泛指谁,而 T 可以泛指比如 Object , **public void printList2(List<T> list){}方法中可以传入 List<Object> list类型参数,也可以传入 List<String> list类型参数,但是 public void printList1(List<Object> list){}就只可以传入 List<Object> list** 类型参数,因为 Object 类型并没有泛指谁,是一个确定的类型.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public  class  TestDifferenceBetweenObjectAndT  {    public  static  void  printList1 (List<Object> list)  {         for  (Object elem : list)             System.out.println(elem + " " );         System.out.println();     }     public  static  <T> void  printList2 (List<T> list)  {         for  (T elem : list)             System.out.println(elem + " " );         System.out.println();     }     public  static  void  printList3 (List<?> list)  {         for  (int  i  =  0 ;i<list.size();i++)             System.out.println(list.get(i) + " " );         System.out.println();     }     public  static  void  main (String[] args)  {         List<Integer> test1 = Arrays.asList(1 , 2 , 3 );         List<String> test2 = Arrays.asList("one" , "two" , "three" );         List<Object> test3 = Arrays.asList(1 , "two" , 1.23 );         List<GenericsFruit.Fruit> test4 = Arrays.asList(new  GenericsFruit .Apple(), new  GenericsFruit .Banana());                  printList1(test3);         printList1(test3);         printList2(test1);         printList2(test2);         printList2(test3);         printList3(test1);         printList3(test2);         printList3(test3);     } } ======== > Task :TestDifferenceBetweenObjectAndT.main() 1  two  1.23  1  two  1.23  1  2  3  one  two  three  1  two  1.23  1  2  3  one  two  three  1  two  1.23  
<T>,Class<T>,Class<?> 的区别前面说过 <T> 是指某个确定的类类型,如 String, Integer, Map 等。
那 ClassClass 也是一个类,但在 Class 存放上 <String>, <List>, <Map> 等类信息的一个类,有点抽象,下面具体分析:
#####Java 当中有 3 种获取 Class 的方式:
调用 Object 类的 getClass() 方法来得到 Class 对象,这也是最常见的产生 Class 对象的方法 :
1 2 List  list  =  null ;Class  clazz1  =  list.getClass();
使用 Class 类的中静态 forName() 方法获得与字符串对应的 Class 对象 :
1 Class  clazz2  =  Class.forName("interview.generics.Dog" );
如果 T 是一个 Java 类型,那么 T.class 就代表了匹配的类对象 :
1 Class  clazz3  =  List.class;
 
Class和 Class<?>` 的使用场景:
使用 Class<T> 和 Class<?> 多发生在反射场景下,先看看如果我们不使用泛型,反射创建一个类是什么样的.
1 2 3 Dog  dog  =  (Dog) Class.forName("interview.generics.Dog" ).newInstance();
使用 Class 泛型后,不用强转了。 
1 2 3 4 5 6 7 8 public  class  TestCreateClassWithGenerics  {    public  static  <T> T createInstance (Class<T> clazz)  throws  IllegalAccessException, InstantiationException {         return  clazz.newInstance();     }     public  static  void  main (String[] args)  throws  InstantiationException, IllegalAccessException {         Dog  dog  =  createInstance(Dog.class);     } } 
#####Class<T> 和 Class<?> 的区别:
Class<T> 在实例化的时候,T 要替换成具体类createInstance(Dog.class),传入的是具体的 **Dog.class**;
Class<?> 它是个通配泛型,? 可以代表任何类型,主要用于声明时的限制情况
1 2 3 4 5 6 7 public  Class<?> clazz1;  public  Class<? extends  Number > clazz2;   public  Class<? super  Number> clazz3;   public  Class<T> clazz4; 
6 泛型的类型擦除详解 前面我们举了一个例子 大概说明了一下什么是泛型的类型擦除:Java 的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
如在代码中定义 List<Object> 和 List<String> 等类型,在编译后都会变成 List,JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 是看不到的。Java 编译器会在编译时尽可能 的发现可能出错的地方,但是仍然无法发现在运行时刻出现的类型转换异常 的情况,类型擦除也是 Java 的泛型与 C++ 模板机制实现方式之间的重要区别。
除了前面提到的那个例子,我们再用一个反射的例子,来说明类型擦除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  class  TestGenericsTypeErasure  {    public  static  void  main (String[] args)  throws  NoSuchMethodException, InvocationTargetException, IllegalAccessException {         ArrayList<Integer> list = new  ArrayList <Integer>();         list.add(1 );         list.getClass().getMethod("add" , Object.class).invoke(list, "asd" );         for  (int  i  =  0 ; i < list.size(); i++) {             System.out.println(list.get(i));         }     } } > Task :TestGenericsTypeErasure.main() 1 asd 
在程序中定义了一个 ArrayList 泛型类型实例化为 Integer 对象,如果直接调用 add() 方法,那么只能存储整数数据,不过当我们利用反射调用 add() 方法的时候,却可以存储字符串,这说明了 Integer 泛型实例在编译之后被擦除掉了,只保留了原始类型。
泛型类型擦除后保留的原始类型 什么是原始类型? 就是在泛型类编译后,擦去了泛型信息,最后在字节码中参数类型的真正类型。
Object 泛型在泛型类中,如果不指定泛型的时候,这个时候的泛型为 Object,就比如 ArrayList 中,如果不指定泛型,那么这个 ArrayList 可以存储任意的对象。
1 2 3 4 ArrayList<Object> list = new  ArrayList ();  list.add(1 ); list.add("121" ); list.add(new  Date ()); 
先检查,再编译以及编译的对象和引用传递问题 先检查,再编译 问:  前面提到过类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量 String 会在编译的时候变为 Object 类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
答: Java 编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
1 2 3 4 5 public  static   void  main (String[] args)  {      ArrayList<String> list = new  ArrayList <String>();       list.add("123" );       list.add(123 ); } 
在上面的程序中,使用 add 方法添加一个整型,在 IDE 中,直接会报错,说明这就是在编译之前的检查,因为如果是在编译之后检查,类型擦除后,原始类型为 Object,是应该允许任意引用类型添加的。可实际上却不是这样的,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。
还是以 ArrayList 为例:
1 2 3 4 5 6 7 8      ArrayList  list1  =  new  ArrayList ();    ArrayList<String> list2 = new  ArrayList <String>();     ArrayList<String> list3 = new  ArrayList <>();      ArrayList  list4  =  new  ArrayList <String>();  
以上代码都能通过编译,不过在第一种情况,可以实现与完全使用泛型参数一样的效果,第二种则没有效果。
因为类型检查就是编译时完成的,new ArrayList() 只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用,因为我们是使用它引用 list1 来调用它的方法,比如说调用 add 方法,所以 list1 引用能完成泛型类型的检查。而引用 list2 没有使用泛型,所以不行。
编译的对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  TestCheckingOfGenerics  {    public  static  void  main (String[] args)  {         ArrayList<String> list1 = new  ArrayList <>();         list1.add("1" );          String  str1  =  list1.get(0 );          ArrayList  list2  =  new  ArrayList <String>();         list2.add("1" );          list2.add(1 );          Object  object  =  list2.get(0 );          new  ArrayList <String>().add("1" );          String  str2  =  new  ArrayList <String>().get(0 );      } } 
通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
引用传递问题 有继承关系的两种情况,引用传递都是不允许的:
ArrayList<String> list1 = new ArrayList<Object>(); //编译错误 
1 2 3 4 5 6 7 8 9 10 11 12 将代码拓展: ```java         ArrayList<Object> list11 = new ArrayList<Object>();         list11.add(new Object());         list11.add(new Object()); //        ArrayList<String> list12 = list11; //编译错误 // ERROR: error: incompatible types: ArrayList<Object> cannot be converted to ArrayList<String>         ArrayList<String> list12 = list11; //编译错误 
```java list2 = new ArrayList(); // 编译错误 
1 2 3 4 5 6 7 8 9 10 11 12 将代码拓展: ```java         ArrayList<String> list21 = new ArrayList<>();         list21.add(new String());         list21.add(new String()); //        ArrayList<Object> list22 = list21; //编译错误 // ERROR: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<Object>         ArrayList<Object> list22 = list21; 
泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以 java 不允许这么干。再说,你如果又用 list21 往里面 add() 新的对象,那么到时候取得时候,我怎么知道我取出来的到底是 String 类型的,还是 Object 类型的呢?
自动类型转换 因为类型擦除,所有的泛型变量在编译以后都转为原始类型了,那为什么我们从泛型集合内获取元素时,不需要强制类型转换? 
以 ArrayList 的 get() 方法为例:
1 2 3 4 5 6 7 8 9 10 11 public  E get (int  index)  {    rangeCheck(index);     return  elementData(index); } @SuppressWarnings("unchecked") E elementData (int  index)  {     return  (E) elementData[index]; } 
可以看到在 retrun 之前,泛型变量会先被强制转换。假设泛型类型变量为 Date,虽然代码编译以后泛型信息会被擦除掉,但是会将 (E) elementData[index],编译为 (Date)elementData[index]。所以我们不用自己进行强转。当存取一个泛型域时也会自动插入强制类型转换。
类型擦除与多态 一个例子:
父类(泛型):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  ClassErasureAndGenerics <T> {    private  T value;     public  void  setValue (T value)  {         this .value = value;     }     public  T getValue ()  {         return  value;     } } 
子类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  class  ClassErasureAndGenericsSub  extends  ClassErasureAndGenerics <Date> {    @Override      public  void  setValue (Date value)  {         super .setValue(value);     }     @Override      public  Date getValue ()  {         return  super .getValue();     }     public  static  void  main (String[] args)  {         ClassErasureAndGenericsSub  sub  =  new  ClassErasureAndGenericsSub ();         sub.setValue(new  Date ());     } } 
按照前面提到,父类编译以后,T 会被转换成原始类型 Object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Mark:generics mark$ javap -c ClassErasureAndGenerics.class   Compiled  from "ClassErasureAndGenerics.java" public  class  interview .generics.ClassErasureAndGenerics<T> {  public  interview.generics.ClassErasureAndGenerics();     Code:        0 : aload_0        1 : invokespecial #1                           4 : return    public  void  setValue (T) ;     Code:        0 : aload_0        1 : aload_1        2 : putfield      #2                           5 : return    public  T getValue () ;     Code:        0 : aload_0        1 : getfield      #2                           4 : areturn } 
那么在子类中的 setValue 方法调用的类型是 Date,参数类型不一致,那在 java 中提到的,如果方法类型不一样,方法名一样,应该是重载,而不是重写,但是重载是发生在同一个类中的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14     public  static  void  main (String[] args)  {         ClassErasureAndGenericsSub  sub  =  new  ClassErasureAndGenericsSub ();         sub.setValue(new  Date ());              } error: no suitable method found for  setValue (Object)          sub.setValue(new  Object ());            ^     method ClassErasureAndGenerics.setValue(Date) is not applicable        (argument mismatch; Object cannot be converted to Date)      method ClassErasureAndGenericsSub.setValue(Date) is not applicable        (argument mismatch; Object cannot be converted to Date)  
以上代码再次证明了,这个确实是重写,而不是重载。
这不是与我们之前解释的理论相矛盾了吗?从 Java 语法上来看,子类的类型是 Data,而父类的类型在反编译中也很清楚是 Object 的,这样应该是重载呀。类型擦除就和多态有了冲突。JVM 知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的 Date 类型参数的方法啊。
再看看 ClassErasureAndGenericsSub.class 的反编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ark:generics mark$ javap -c ClassErasureAndGenericsSub.class   Compiled  from "ClassErasureAndGenericsSub.java" public  class  interview .generics.ClassErasureAndGenericsSub extends  interview .generics.ClassErasureAndGenerics<java.util.Date> {  public  interview.generics.ClassErasureAndGenericsSub();     Code:        0 : aload_0        1 : invokespecial #1                           4 : return    public  void  setValue (java.util.Date) ;          Code:        0 : aload_0        1 : aload_1        2 : invokespecial #2                           5 : return         public  void  setValue (java.lang.Object) ;     Code:        0 : aload_0        1 : aload_1        2 : checkcast     #4                           5 : invokevirtual #8                           8 : return               -----------------------------------------------                     public  java.lang.Object getValue () ;            Code:        0 : aload_0        1 : invokevirtual #9                           4 : areturn   public  java.util.Date getValue () ;              Code:        0 : aload_0        1 : invokespecial #3                           4 : checkcast     #4                           7 : areturn } 
从编译的结果来看,我们本意重写 setValue 和 getValue 方法的子类,竟然有 4 个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法 。可以看到桥方法的参数类型都是 Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的 setvalue 和 getValue 方法上面的 @Oveerride 只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。
不过,要提到一点,这里面的 setValue 和 getValue 这两个桥方法的意义又有不同。
setValue 方法是为了解决类型擦除与多态之间的冲突。
而 getValue 却有普遍的意义。
即如果这是一个普通的继承关系,那么父类的 getValue 方法如下:
1 2 3 public  Object getValue ()  {      return  super .getValue();   } 
子类方法:
1 2 3 public  Date getValue ()  {      return  super .getValue();   } 
这在普通的类继承中也是普遍存在的重写。
为什么泛型变量不能是基本数据类型 因为类型擦除以后,假设原始类型转变为 Object,而 Object 变量中无法存基本类型。
如: 没有 ArrayList<double>,只有 ArrayList<Double>。因为当类型擦除后,ArrayList 的原始类型变为 Object,但是 Object 类型不能存储 double 值,只能引用 Double 的值。
为什么泛型类型不能使用 instanceof 1 2 3 4 Integer  val  =  1 ;if  (val instanceof  GenericClass<Integer>) {    System.out.println("test" ); } 
因为类型擦除后,Integer 被转成原始类型,泛型信息 Integer 已经不存在了。
7 参考