基础篇:深入解析Java泛型

开发 后端
先了解下java的Type类型体系(类的类=>类型),Type是所有类型(原生类型-Class、参数化类型-Parameterizedtype、数组类型-GenericArrayType、类型变量-TypeVariable、基本类型-Class)的共同接口;前两篇反射和注解讲到的Class就是Type的一实现类。

[[347300]]

本文转载自微信公众号「潜行前行」,作者cscw 。转载本文请联系潜行前行公众号。  

 1 JAVA的Type类型体系

先了解下java的Type类型体系(类的类=>类型),Type是所有类型(原生类型-Class、参数化类型-Parameterizedtype、数组类型-GenericArrayType、类型变量-TypeVariable、基本类型-Class)的共同接口;前两篇反射和注解讲到的Class就是Type的一实现类

  • Type下面又有四个子接口类ParameterizedType、TypeVariable、GenericArrayType、WildcardType
    • List表示泛型,E是TypeVariable类型,List则是ParameterizedType(参数化类型),List里的String称为实际参数类型
    • 具体化泛型中的类型时,可以使用 ? extends 或 ? super来表示继承关系;如List,而里面的 ? 称为通配符类型WildcardType
    • GenericArrayType 表示一种元素类型是ParameterizedType(参数化类型)或者TypeVariable(类型变量)的数组类型,如T[] 或者 List[]
  • 注解是JDK1.5才出现了的,为了表示被注解的类型的,加入AnnotatedElement类型,字面意思就是被注解的元素。JDK1.8又有了AnnotatedType将Type和被注解元素的概念关联起来。
  • AnnotatedType也有四个子接口,和Type的四个子接口一一对应,如:ParameterizedType类型被注解则被编译器解析成AnnotatedParameterizedType: @AnTest("list")Listlist

2 泛型的概念

  • Java 泛型(generics)是JDK1.5中引入的一个新特性,其本质是参数化类型,解决不确定具体对象类型的问题;其所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法

泛型: 把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型

3 泛型类和泛型方法的示例

  • 泛型类的定义
  1. public class MainTest<T> { 
  2.     private  T param; 
  3. public static void main(String[] args){ 
  4.         MainTest<String> data = new MainTest<String>(){}; 
  5.         ParameterizedType genType1 = (ParameterizedType)data.getClass().getGenericSuperclass(); 
  6.   } 
  • 泛型方法的定义
  1. public class MainTest{ 
  2.     public static void main(String[] args){ 
  3.         printData("siting"); 
  4.     } 
  5.     static  <T> T printData(T t){ 
  6.         System.out.println(t); 
  7.         return t; 
  8.     } 

接口和抽象类都可以使用泛型

4 类型擦除

  • 创建泛型的实例时,jvm是会把具体类型擦除的;编译生成的字节码中不包含泛型中的类型参数,即ArrayList和ArrayList都擦除成了ArrayList,也就是被擦除成"原生类型",这就是泛型擦除
  1. public class MainTest { 
  2.     public static void main(String[] args){ 
  3.         List<String> strArr  = new ArrayList<>(); 
  4.         List<Integer> intArr  = new ArrayList<>(); 
  5.         Type strClazz = strArr.getClass(); 
  6.         Type intClazz = intArr.getClass(); 
  7.     } 

  • 查看编译后的字节码文件是如何表示的: idea菜单 -> view -> show ByteCode
  1. public class MainTest<T> { 
  2.     T param; 
  3.     public static void main(String[] args){ 
  4.         MainTest<String> test = new MainTest<>(); 
  5.         test.setParam("siting"); 
  6.     } 
  7.     public T getParam() {  return param;   } 
  8.     public void setParam(T param) {  this.param = param;  } 
  9. public class com/MainTest { 
  10.   ...省略 
  11.   public static main([Ljava/lang/String;)V 
  12.    L0 
  13.     LINENUMBER 7 L0 
  14.     NEW com/MainTest 
  15.     DUP 
  16.     INVOKESPECIAL com/MainTest.<init> ()V 
  17.     ASTORE 1 
  18.    L1 
  19.     LINENUMBER 8 L1 
  20.     ALOAD 1 
  21.     LDC "siting"     // 调用类型擦除后的setParam(Object) 
  22.     INVOKEVIRTUAL com/MainTest.setParam (Ljava/lang/Object;)V 
  23.    L2 
  24.    ...省略//getParam 的返回值是Object 
  25.   public getParam()Ljava/lang/Object; 
  26.    L0 
  27.     LINENUMBER 10 L0 
  28.     ALOAD 0 
  29.     GETFIELD com/MainTest.param : Ljava/lang/Object; 
  30.     ARETURN 
  31.    ...省略//setParam 的入参是Object 
  32.   public setParam(Ljava/lang/Object;)V 
  33.    L0 
  34.     LINENUMBER 11 L0 
  35.     ALOAD 0 
  36.     ALOAD 1 
  37.     PUTFIELD com/MainTest.param : Ljava/lang/Object; 
  38.     RETURN 
  39.    ... 

可以看出T(String)都被转换为Object类型,最初的初始化的String不见了

5 泛型的继承

  • 子类可以指定父类的泛型参数,可以是已知类(Integer、String等),也可以用子类自己的泛型参数指定
  • 泛型被继承时,且指定父类泛型参数,则额外生成的ParameterizedType类型作为子类的父类;如果没有指定父类泛型参数,则直接继承原生类型
  1. public class MainTest<T> { 
  2.     T param; 
  3.     static public class SubTest1 extends MainTest<String>{} 
  4.     static public class SubTest2<R> extends MainTest<R>{} 
  5.     //SubTest3继承的时原生类型 
  6.     static public class SubTest3 extends MainTest{} 

6 泛型变量TypeVariable

  • (先临时定义一个名称,Test里的E为泛型参数);泛型变量TypeVariable:泛型的泛型参数就是TypeVariable;当父类使用子类的泛型参数指定自身的泛型参数时;或者泛型属性定义在泛型类A中,并使用泛型类A的泛型参数T时,其泛型参数都会被编译器定为泛型变量TypeVariable,而不是被擦除
  1. public class MainTest<T> { 
  2.     List<T> param; 
  3.     public static void main(String[] args) throws Exception{ 
  4.         Class clazz =  MainTest.class; 
  5.         TypeVariable[] typeVariable = clazz.getTypeParameters(); 
  6.         // 1 
  7.         Field field = clazz.getDeclaredField("param"); 
  8.         ParameterizedType arrayType = (ParameterizedType)field.getGenericType(); 
  9.         // interface List<E> 的泛型类型E被T,具体化,因此其被识别为 TypeVariable 
  10.         TypeVariable variable1 = (TypeVariable)arrayType.getActualTypeArguments()[0]; 
  11.         // 2 
  12.         ParameterizedType type = (ParameterizedType)SubTest.class.getGenericSuperclass(); 
  13.         TypeVariable variable2 = (TypeVariable)type.getActualTypeArguments()[0]; 
  14.     } 
  15.     static class SubTest<R> extends MainTest<R>{} 

7 参数化类型ParameterizedType

  1. public interface ParameterizedType extends Type { 
  2.     //获取实际参数,List<String>里的String; 如果是List<T>则是TypeVariable类型 
  3.     Type[] getActualTypeArguments();  
  4.     // 获取原始类型List<String> -> List<E> 
  5.     Type getRawType();   
  6.     Type getOwnerType(); 
  • 需要注意的点,我们不能直接获取指定具体参数的泛型的类型,如Class clazz = List.class编译时不通过的;还有就是直接通过泛型类new创建的对象,其Class并非ParameterizedType类型,而是泛型本身的class,示例如下
  1. public class MainTest<T> { 
  2.     public static void main(String[] args){ 
  3.         MainTest<String> str = new MainTest<String>(); 
  4.         Class variable = str.getClass(); 
  5.         Type genType1 = variable.getGenericSuperclass(); 
  6.     } 

  • 被具体参数化的泛型才能被编译器识别为ParameterizedType类型,有三种方式获取ParameterizedType类型
  1. // 1 子类继承泛型时,指定具体参数(可以是String等已知类型,也可以是子类的泛型参数) 
  2. // 2 获取在类内部定义的泛型属性,需指定具体泛型参数 
  3. // 3 局部代码,可以通过泛型的匿名内部子类(需指定具体泛型参数)获取ParameterizedType类型 
  4. public class MainTest<T> { 
  5.     List<T> list; 
  6.     public static void main(String[] args) throws NoSuchFieldException { 
  7.         SubTest<String> str = new SubTest<>(); 
  8.         // 方式一 
  9.         Class variable = str.getClass(); 
  10.         // 父类是(521)ParameterizedType类型 
  11.         ParameterizedType genType = (ParameterizedType)variable.getGenericSuperclass(); 
  12.         // (521)ParameterizedType类型的原生类型是(479)class com.MainTest 
  13.         Type clazz = genType.getRawType(); 
  14.         //MainTest.class 的原生类型是 (479)class com.MainTest 
  15.         Class rawClazz = MainTest.class; 
  16.  
  17.         //方式二,泛型属性 
  18.         Field field = rawClazz.getDeclaredField("list"); 
  19.         //属性list 类型是(546)ParameterizedType类型List<T> 
  20.         ParameterizedType fieldType = (ParameterizedType)field.getGenericType(); 
  21.  
  22.         // 方式三 
  23.         MainTest<String> sub3 = new MainTest<String>(){}; 
  24.         // clazz3是匿名子类 
  25.         Class clazz3 =  sub3.getClass(); 
  26.         //父类是(555)ParameterizedType类型 
  27.         ParameterizedType genType3 = (ParameterizedType) clazz3.getGenericSuperclass(); 
  28.         // (555)ParameterizedType类型的原生类型是(479)class com.MainTest 
  29.         Type type3 = genType3.getRawType(); 
  30.     } 
  31.     public static class SubTest<R> extends MainTest<R>{ } 

8 通配符(WildcardType)

无边界通配符:无界通配符 ? 可以适配任何引用类型:

  • 当方法参数需要传入一个泛型时,而且无法确定其类型时。直接使用无具体泛型变量的泛型,容易造成安全隐患;若在方法代码里进行类型转换,极容易出现ClassCastException错误
  • 那泛型变量用Object代替不就行了?但是泛型类+具体参数转变的ParameterizedType(参数化类型)是不存在继承关系;即Object是String的父类,但是List 和List的类型是不同的两个ParameterizedType,不存在继承关系。于是有了类型通配符 ?
  1. public static void print(List list){}  
  2. ----->>> 
  3. public static void print(List<?> list){}  

  • 无界通配符可以匹配任意类型;但是在使用?时,不能给泛型类的变量设置值,因为我们不知道具体类型是什么;如果强行设置新值,后面的读容易出现ClassCastException错误。因此编译器限制了**通配符 ?**的泛型只能读不能写

上界限定通配符 < ? extends E>

  • 想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做?可以使用List,表明List里的元素都是Number的子类
    1. public static void print(List<? extends Number> list) { 
    2.         Number n = new Double("1.0"); 
    3.         list.add(n); 
    4.         Number tmp = list.get(0); 
    5.     } 

  • 图片里可以看出,存在上界通配符,因为具体类型不确定,也是只能读不能写的

下界限定通配符 < ? super E>

  1. class Parent{ } 
  2. class Child extends Parent{ } 
  3. public class MainTest<T> { 
  4.     T param; 
  5.     public static void main(String[] args){ 
  6.         MainTest<? super Child> parent_m = new MainTest<>(); 
  7.         parent_m.setParam(new Child()); 
  8.         Object parent = parent_m.getParam(); 
  9.     } 
  10.     public T getParam() {  return param;  } 
  11.     public void setParam(T param) {  this.param = param; } 

  • 如果定义了通配符是谁的父类,则是下界限定通配符;此类通配符可读可写,转成任意父类都不会出现ClassCastException错误。
  • 个人猜想:难道是因为通配符和上界限定通配符的泛型 向下转型容易出现ClassCastException错误,而下界限定通配符向上转型不会出现ClassCastException错误,因此java规范限制前者编译出错,而后面编译通过?

9 泛型数组(GenericArrayType)

  1. public interface GenericArrayType extends Type { 
  2.     //获得这个数组元素类型,即获得:A<T>(A<T>[])或  T(T[]) 
  3.     Type getGenericComponentType(); 
  • GenericArrayType,泛型数组,描述的是ParameterizedType类型以及TypeVariable类型数组,即形如:Test[][]、T[]等,是GenericArrayType的子接口
  1. public class MainTest<T> { 
  2.     T[] param; 
  3.     public static void main(String[] args) throws Exception{ 
  4.         Class clazz =  MainTest.class; 
  5.         Field field = clazz.getDeclaredField("param"); 
  6.         GenericArrayType arrayType = (GenericArrayType)field.getGenericType(); 
  7.         TypeVariable variable = (TypeVariable) arrayType.getGenericComponentType(); 
  8.     } 

 

责任编辑:武晓燕 来源: 潜行前行
相关推荐

2017-11-14 14:41:11

Java泛型IO

2024-10-22 16:59:07

2024-11-05 09:11:09

TypeScript开发者代码

2019-09-04 00:20:10

JSON泛型擦除

2009-09-25 10:03:51

Java泛型

2021-05-10 07:38:09

Swift 泛型Tips

2009-08-24 18:22:05

C# 泛型编程

2021-12-30 19:34:15

Java泛型JDK

2011-06-03 08:49:54

Java

2009-08-24 17:07:09

C# 泛型

2020-11-10 07:09:59

Java泛型语言

2021-06-17 06:51:32

Java泛型Java编程

2021-07-01 06:47:30

Java泛型泛型擦除

2017-03-06 16:51:52

Java泛型实现

2021-09-29 18:17:30

Go泛型语言

2021-06-18 08:25:42

Java泛型通配符

2011-03-21 16:26:28

java泛型

2016-10-31 19:41:29

Java垃圾回收

2021-07-09 06:11:37

Java泛型Object类型

2020-11-30 06:27:35

Java泛型Object
点赞
收藏

51CTO技术栈公众号