星空网 > 软件开发 > Java

泛型的理解及应用(一):泛型擦除

  在笔者工作过程中,大略地知道Java在泛型设计上是一种“伪泛型”,存在着泛型擦除。在使用Gson编写工具类之前,我一直错误地认为:泛型的擦除就是把泛型内的实参全部替换成Object或者直接消灭泛型实参后生成Java的字节码文件。但我的工作笔记上面清楚地写着解决泛型擦除使用的两个方法:①在构造器内传递泛型相关类型 ②使用反射获取泛型实参

  这一对比,擦除泛型实参生成字节码文件与使用反射获取泛型实参,这两个是矛盾的。如果能够使用反射获取出泛型实参,那么字节码文件肯定没有对泛型实参进行擦除。那泛型的擦除这块,虚拟机把泛型擦除了什么?在写这文章之前,一直把学到的东西推翻了再推翻,因此在这里写一些总结,免得以后自己再继续走一些弯路。

  首先编写一些泛型类以及泛型方法、泛型变量。通过反编译工具对class文件进行反编译,查看class里面泛型实参是否真被擦除了。

  因此构思内容如下:

  1.编写一个水果的类,分别用草莓、苹果、橙几个类去继承水果类

  2.编写销售员泛型类,类内含有泛型方法、泛型字段,以及泛型继承子类

  3.编写测试代码,使用反射获取泛型类内的各项内容,查看是否能够获取相关的泛型

泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
package com.genric.domain;/** * @Author: Travelsky_CLSUN * @Date: Created on 17-5-8.下午3:39 * @Description: 水果类 */public class Fruit{  private String name;  private String color;  public String getName()  {    return name;  }  public void setName(String name)  {    this.name = name;  }  public String getColor()  {    return color;  }  public void setColor(String color)  {    this.color = color;  }  public void showInfo()  {    System.out.println(this.name + ":" + this.color);  }}public class Strawberry extends Fruit{}public class Apple extends Fruit{}public class Orange extends Fruit{}

编写水果类及其四个子类
泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
package com.genric.domain;import java.util.ArrayList;import java.util.List;/** * @Author: Travelsky_CLSUN * @Date: Created on 17-5-8 * @Description: 泛型销售员群体,该群体只卖水果 */public class Sellers<T extends Fruit>{  // 使用属性记录售卖某种水果的最佳日期  public List<Strawberry> BerryDateList = new ArrayList<Strawberry>();  public List<Apple> AppleDateList = new ArrayList<Apple>();  public List<Orange> OrangeDateList = new ArrayList<Orange>();  // 获取具体的水果品种售卖时间列表  public List<Apple> getAppleList(List<Apple> eList)  {    ArrayList<Apple> appleList  =  new  ArrayList<Apple> ();return null;  }  // 使用泛型extends解决调用实体方法名称带来的异常  public void getInfo(T t)  {    t.showInfo();  }  // 获取某种类水果日期销售列表  public List<T> getList(List<T> eProduuctList)  {    return null;  }  public static <T> T getListByStatic(List<T> eList)  {    return null;  }}/** * @Author: Travelsky_CLSUN * @Date: Created on 17-5-8 * @Description: 卖橙的个体户 * 在继承泛型类的时候必须明确继承类的泛型实参,如果父类属于不确定的泛型内容,则必须把子类也声明为不确定的泛型 * eg:class SelfEmployed extends Sellers<T> 错误,必须指明子类的泛型参数 * 正确签名如下: * 1.class SelfEmployed<T> extends Sellers<T> * 2.class SelfEmployed extends Sellers<Fruit> **/public class SelfEmployed extends Sellers<Orange>{}

销售员泛型类及其子类
泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
 1 package com.genric.test; 2  3 import java.lang.reflect.Field; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.ParameterizedType; 6 import java.util.List; 7  8 import org.junit.Test; 9 10 import com.genric.domain.SelfEmployed;11 import com.genric.domain.Sellers;12 13 /**14  * @Author: Travelsky_CLSUN15  * @Date: Created on 17-5-816  * @Description: 测试类17 */18 public class TestGenric19 {20   /**21    * @Author: Travelsky_CLSUN22    * @Date: Created on 17-5-823    * @Description: 测试泛型实参是否被擦除24   */25   @Test26   public void testGenricTypeExit() throws Exception27   {28     Class<?> sellersTemp = Sellers.class;29     ParameterizedType actualType = null;30     log("====根据反射获取泛型类、泛型字段以及泛型方法的类型====");31     // 测试使用反射获取泛型类的实际参数32     log("泛型类Sellers的泛型实参:" + sellersTemp.getTypeParameters()[0].getBounds()[0].toString());33     // 测试使用反射获取泛型类下泛型变量的实际参数34     Field fieldBerry = sellersTemp.getField("BerryDateList");35     log("泛型字段BerryDateList的类型:" + fieldBerry.getGenericType().toString());36     Method methodAppleList = sellersTemp.getMethod("getAppleList", new Class[]37     { List.class });38     actualType = (ParameterizedType) methodAppleList.getGenericParameterTypes()[0];39     log("泛型方法getAppleList的形式参数的类型:" + actualType.getActualTypeArguments()[0].toString());40     actualType = (ParameterizedType) methodAppleList.getGenericReturnType();41     log("泛型方法getAppleList的返回值的类型:" + actualType.getActualTypeArguments()[0].toString());42     Class<?> selfEmployed = SelfEmployed.class;43     actualType = (ParameterizedType) selfEmployed.getGenericSuperclass();44     log("泛型子类SelfEmployed的主类实参:" + actualType.getActualTypeArguments()[0]);45 46   }47 48   /**49    * @Author: Travelsky_CLSUN50    * @Date: Created on 17-5-851    * @Description: 输出函数52   */53   private void log(String str)54   {55     System.out.println(str);56   }57 }

测试类,测试是否可以通过反射获取泛型参数

  如果泛型真的是全部擦除的话,通过反射应该没法获取到相关的泛型实参。但是运行测试代码,发现结果如下:

泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
1 ====根据反射获取泛型类、泛型字段以及泛型方法的类型====2 泛型类Sellers的泛型实参:class com.genric.domain.Fruit3 泛型字段BerryDateList的类型:java.util.List<com.genric.domain.StrawBerry>4 泛型方法getAppleList的形式参数的类型:class com.genric.domain.Apple5 泛型方法getAppleList的返回值的类型:class com.genric.domain.Apple6 泛型子类SelfEmployed的主类实参:class com.genric.domain.Orange

通过反射获取到的结果

  再使用JD-gui工具对Sellers泛型类进行反编译,发现结果如下:

         泛型的理解及应用(一):泛型擦除

  从这反编译的内容上看,给了我一个错觉:泛型擦除没有把类以及方法、字段等元数据下的实参给擦除,只是擦除了方法内部的泛型实参(见getAppleList方法内的appleList)。

     基于这个错觉,我觉得可以在Sellers这个泛型类下面编写签名为 public List<T> getList(List<T> eProduuctList) 的方法,通过反射动态地获取到eProductList下的具体类型,从而不用再去写好几个获取某个品种最佳销售日期的函数(类似getAppleList这样的每次写一个获取相关实体列表的函数)。

  所以,把getList方法写成了下面的样子,在反射的时候动态地获取泛型实参T类型。    

泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
 1   // 获取某种类水果日期销售列表 2   public List<T> getList(List<T> eProductList) 3   { 4     Class<?> paramClass = eProductList.getClass(); 5     System.out.println("before transfromer: " + paramClass); 6     Type paramType = paramClass.getTypeParameters()[0].getBounds()[0]; 7     if (paramType instanceof ParameterizedType) 8     { 9       System.out.println(paramType);10       ParameterizedType genricType = (ParameterizedType) ((ParameterizedType) paramType)11           .getActualTypeArguments()[0];12       System.out.println("actualType of eProductList is :" + genricType);13     }14     else15     {16       System.out.println("Type of eProductList is :" + paramType);17     }18     return null;19   }

获取水果销售时间列表泛型方法(查看泛型实参)
泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
1   // 测试在泛型方法内动态获取泛型实参2   @Test3   public void testGetParameterType() throws Exception4   {5     Sellers<Apple> appleSellers = new Sellers<Apple>();6     appleSellers.getList(new ArrayList<Apple>());7   }

使用客户端代码测试获取获取泛型实参
泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
1 before transfromer: class java.util.ArrayList2 Type of eProductList is :class java.lang.Object

返回结果

  由返回结果上面看来,通过反射根本不能动态地获取方法内的泛型实参。那为什么不能获取到相关的泛型实参,通过反射的时候获取到的是一个Object实参?我想了几个可能导致出现这个结果的猜想:①泛型擦除 ②方法是个动态方法,没关联到类的信息。

  在这两个思考原因论证处理之前,先来回复无法使用反射该泛型参数的原因:Java在编译时会校验泛型参数,生成时会以泛型实参的上限类型替代真实的泛型实参。但,Java虚拟机会以签名的形式保留这些泛型实参类型(包括类的定义、泛型方法、泛型字段都会保留参数的签名信息)

  因为保留的是签名,所以没法使用反射正确地获取到泛型的实际参数。假如把泛型类定义为Sellers<T>,假如在客户端代码传入了Sellers<Fruit>;同样地使用反射没法获取到Fruit这个泛型类实参,最多只能获取到一个"T"这样继承于Object的参数(只能获取到明确已经定义好的参数类型)。

  关于猜想的第二点,获取不到是因为没关联到相关类的信息。这个猜想就有趣了。有这么多不确定性,原因都只有一个:在平时的使用里没多关注底层内容。在这个猜想下,我添加了一个静态的泛型方法。期望透过静态方法去查看是否存在泛型参数。于是有:

泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
 1   // 测试泛型的静态方法 2   public static <T> T getListByStatic(List<T> eList) 3   { 4     Class<?> paramClass = eList.getClass(); 5     System.out.println("before transfromer: " + paramClass); 6     Type paramType = paramClass.getTypeParameters()[0].getBounds()[0]; 7     if (paramType instanceof ParameterizedType) 8     { 9       System.out.println(paramType);10       ParameterizedType genricType = (ParameterizedType) ((ParameterizedType) paramType)11           .getActualTypeArguments()[0];12       System.out.println("actualType of eList is :" + genricType);13     }14     else15     {16       System.out.println("Type of eList is :" + paramType);17     }18     return null;19   }

定义静态泛型方法

   在泛型类调用这个静态方法的时候,编译器报错了!

   泛型的理解及应用(一):泛型擦除

  这里需要指出的是:

  1.关于静态泛型方法的声明.

  静态泛型方法不能访问泛型类上面的变量。也就是说静态泛型方法不能因为方法定义在泛型类Seller<T>下而把静态方法签名写成以下样子:

public static T getListByStatic(List<T> eList) ,原因:静态方法使用的必须为静态引用,泛型实参T要在对象创建的时候才能确定下来,所以必须显式地把泛型引用变量显式定义在静态泛型方法上,例如  public static <E> E getListByStatic(List<E> eList) . 所以在泛型类下里面定义的静态泛型方法可能泛型参数T跟泛型类的T是一样的,但是代表的是两个不同的类型符号,而且该静态泛型方法也不会识别到泛型类中的变量T.

  2.关于静态泛型方法的调用.

  正如上面泛型类的声明所说,泛型实参T要在对象创建的时候才能确定下来。使用"类名<泛型实参>.静态方法名"这种方法去调用泛型静态方法根本没法调用,因为泛型方法根本不认识定义的泛型实参代表的类。只能通过"类名.方法名"的形式调用泛型静态方法。所以截图上面的调用应该更正为   Sellers.getListByStatic(new ArrayList<Apple>()) 

  同样地使用静态泛型方法也不能动态获取到泛型参数的信息。在泛型静态方法的编写后,突然发现其实根本不用从方法参数内获取泛型实参,因为写的非静态方法全部都是跟泛型类挂钩的,只要能获取到泛型类的泛型实参就可以获取到方法内编写的实参(非泛型类下的方法实参真的没办法获取出来,除了传实参的类类型)。

  那么有趣的来了,怎么能获取到泛型类里面的泛型实参呢,肯定有办法的!因为使用Gson时,Google使用了一个叫TokenType的对象用来捕获泛型实参信息。如果没法通过反射获取到的话,Google是使用何种方法把泛型实参的信息保存下来的?

  在Google的Gson、Guice框架等里面用了比较多捕获泛型实参的方法,基本都是通过创建一个匿名类来获取的。不需要使用反射进行操作,原理也是通过获取类的声明得到某个泛型类的实参。因为匿名内部类会以子类的方式把主类的签名信息保存下来,从而达到类似"动态"获取泛型实参。

泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
 1   // 测试通过匿名内部类获取泛型实参 2   @Test 3   public void testGetTypeByAnonymousClass() 4   { 5     Sellers<Apple> sellers = null; 6     // 主类类型 7     Type sellerType = null; 8     // 泛型实参类型 9     Type paramType = null;10     log("-------非匿名子类+反射获取到的类类型---------");11     sellers = new Sellers<Apple>();12     sellerType = sellers.getClass().getGenericSuperclass();13     log("Sellers主类带参数泛型实体为:" + sellerType.toString());14     // 无法转换,在方法体内定义的泛型实参,均向上擦除为Object(无签名)15     // paramType = ((ParameterizedType) sellerType).getActualTypeArguments()[0];16     log("--------匿名子类+反射获取到的类类型---------");17     sellers = new Sellers<Apple>()18     {19     };20     sellerType = sellers.getClass().getGenericSuperclass();21     // 返回T则说明没法通过反射动态获取到泛型实参22     log("Sellers主类带参数泛型实体为:" + sellerType.toString());23     paramType = ((ParameterizedType) sellerType).getActualTypeArguments()[0];24     log("泛型实体的类型为:" + paramType);25   }

对比匿名类和直接通过泛型类获取实参结果

  正如上面所说,泛型实参只会在类、字段及方法参数内保存其签名。在方法体内的泛型实参均不作任何保留统统擦除。但是在测试匿名子类的代码里面,获取到了泛型实参。结果如下:

泛型的理解及应用(一):泛型擦除泛型的理解及应用(一):泛型擦除
1 -------非匿名子类+反射获取到的类类型---------2 Sellers主类带参数泛型实体为:class java.lang.Object3 --------匿名子类+反射获取到的类类型---------4 Sellers主类带参数泛型实体为:com.genric.domain.Sellers<com.genric.domain.Apple>5 泛型实体的类型为:class com.genric.domain.Apple

对比结果

  在文章末尾,再总结下个人对Java泛型使用的一些看法:

 1.泛型实参只会在类、字段及方法参数内保存其签名,无法通过反射动态获取泛型实例的具体实参。

   2.需要获取泛型实参的情况下,方法有三;①通过传递实参类型 ②明确定义泛型实参类型,通过反射获取签名 ③通过匿名类捕获相关的泛型实参

  最后,附上参考文章及部分论点:

  1.泛型的擦除及泛型实参存储位置

  2.泛型的擦除了什么

  3.静态泛型方法无法使用泛型类实体参数及其原因

  4.匿名内部类的存在意义及使用

  5.匿名内部类详解

  6.匿名子类获取泛型实参

  7.使用反射获取泛型方法签名

  附件:源码及测试代码下载




原标题:泛型的理解及应用(一):泛型擦除

关键词:

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

这个子品类零售额高涨176%!好卖的同时也要注意侵权问题,谨慎选品!:https://www.kjdsnews.com/a/1791396.html
价值20亿美元的AI社交新贵?米哈游、腾讯争相投资MiniMax:https://www.kjdsnews.com/a/1791397.html
亚马逊英国站严查IEN码,走的是包税,这个编号怎么填?:https://www.kjdsnews.com/a/1791398.html
TikTok Shop八大新站点内测启动,电商界又要热闹啦!:https://www.kjdsnews.com/a/1791399.html
2024年,千万别再错过TikTok了:https://www.kjdsnews.com/a/1791400.html
庄俊:小红书爆文套路 :https://www.kjdsnews.com/a/1791401.html
深圳到西安自驾路线攻略 深圳到西安自驾最佳路线:https://www.vstour.cn/a/411228.html
松花蛋是哪里的特产松花蛋的产地:https://www.vstour.cn/a/411229.html
相关文章
我的浏览记录
最新相关资讯
海外公司注册 | 跨境电商服务平台 | 深圳旅行社 | 东南亚物流