java 泛型擦除

红太狼 2022-10-29 11:17 314阅读 0赞

前言

本文讲从字节码层面深度学习泛型,这也是大厂常见面试

我们定义了如下类图所示类:
在这里插入图片描述
在这里插入图片描述

泛型的协变与逆变

定义
在这里插入图片描述

泛型 extend的作用

请先浏览如下代码

  1. List<? extends Female> extenfemalesList1 = new ArrayList<Female>();
  2. // List<? extends Female> extenfemalesList2 = new ArrayList<Human>();//error
  3. // List<? extends Female> extenfemalesList3 = new ArrayList<Object>();//error
  4. List<? extends Female> extenfemalesList4 = new ArrayList<FemaleTeacher>();
  5. Female female = extenfemalesList1.get(0);
  6. // extenfemalesList1.add(new Female()); //erro

List<? extends Female> 表示这个集合装载的Female之下的对象集合。也就是说这个集合可能是一个new ArrayList<Female>(),new ArrayList<FemaleTeacher>()
在这里插入图片描述

根据这个道理,我们取出来的数据一定Female对象或者其自类。
因此以下代码顺利编译通过:

  1. Female female = extenfemalesList1.get(0);

而如下代码必然出错,因为List<? extends Female>无法确定你的集合到底是new ArrayList<Female>()还是new ArrayList<FemaleTeacher>(),如果你是new ArrayList<FemaleTeacher>()那么放入必然引起错误,所以这里编译器无法判断因此抛出错误。

  1. extenfemalesList1.add(new Female());

总结:泛型extend作用就是协变的概念,它用于表示其集合可能是继承泛型的子集合。

泛型 super的作用

  1. List<? super Female> extenfemalesList1 = new ArrayList<Female>();
  2. List<? super Female> extenfemalesList2 = new ArrayList<Human>();
  3. List<? super Female> extenfemalesList3 = new ArrayList<Object>();
  4. // List<? super Female> extenfemalesList4 = new ArrayList<FemaleTeacher>();//erro
  5. // Female female = extenfemalesList1.get(0);//erro
  6. extenfemalesList1.add(new Female());
  7. Object female = extenfemalesList1.get(0);

其概念图如下:
在这里插入图片描述
List<? super Female>表示这个集合可能是这个泛型的父亲的集合类型。

所以以下代码顺利编译

  1. extenfemalesList1.add(new Female());//不过是哪个父集合类型都不违背语义
  2. Object female = extenfemalesList1.get(0);//不管父集合类型一定都满足是一个Object对象

违背语义报错代码

  1. //取出来的可能是Object Female Human类型,所以无法确定Female类型
  2. Female female = extenfemalesList1.get(0);//erro

总结:泛型super作用就是逆变的概念

泛型擦除

何为泛型擦除?JVM运行会把泛型视为Object对象,这里特别注意这里针对的是JVM运行时。但是泛型信息依然保留在字节码层面,但是根据不同语法形式我们保存的位置不同。

泛型函数

  1. static void test(List<FemaleTeacher> listParameter) {
  2. FemaleTeacher femaleTeacher = listParameter.get(0);
  3. System.out.println(femaleTeacher);
  4. }

请问如上代码是否可以在运行期拿到泛型的具体类型?
泛型擦除体现在哪?
在这里插入图片描述

JVM调用指令说明:
在这里插入图片描述
我们可以看到JVM最终的实现伪代码如下:

  1. static void test(List<Object> listParameter) {
  2. Object femaleTeacher = listParameter.get(0);
  3. FemaleTeacher femaleTeacher1 = (FemaleTeacher) femaleTeacher;
  4. System.out.println(femaleTeacher1);
  5. }

这便是泛型擦除。但是你仔细发现上图有一个LocalVariableTypeTable内部却显示了泛型
在这里插入图片描述

LocalVariableTypeTable存储于字节码方法区的code类型attribute_infoattribute中.
在这里插入图片描述

所以我们可以通过反射获取即可
具体可以看网上的其他文章
https://www.cnblogs.com/wwjj4811/p/12592443.html

泛型函数会比其他不含泛型的函数额外多一个signature类型的attribute_info属性存在medhod_info
在这里插入图片描述
在这里插入图片描述
这里解释下descriptorsignature的区别:
如果你在编写ASM框架的时候遇到这两个参数,如果你的字节码方法不含泛型signature直接写空字符串即可。

泛型类

  1. public class MyClassA<T> {
  2. public void say(T t) {
  3. System.out.println(t);
  4. }
  5. }
  6. public class MyClassB extends MyClassA<Human> {
  7. }

我们直接看MyClassB编译后信息:
在这里插入图片描述

泛型类会在字节码的attribute_info表多输一个attriute_info
在这里插入图片描述
所以可以获取到类的泛型具体可参考如下链接

  1. https://www.cnblogs.com/one777/p/7833789.html
  2. https://blog.csdn.net/u011082160/article/details/101025884

type

预备知识

同过上文的分析,我们知道JVM会在运行时才体现泛型擦除机制,而泛型信息存在class中,为了拿到这些信息,java1.5推出了Type类型让我们通过反射拿取.
在这里插入图片描述

这里给出一个小结论或者小知识,如果你通过反射拿到的type类型为class,那么证明没有泛型信息.

一个小Demo希望对大家理解有帮助

  1. interface GeneircInteface {
  2. }
  3. class MyTestClass implements GeneircInteface {
  4. }
  5. public class Java {
  6. public static void main(String[] args) {
  7. //getGenericInterfaces获取接口上的泛型信息,如果没有实现接口返回的数组为0
  8. //如果想得到继承类的泛型信息可以用getGenericSuperclass
  9. Type[] genericInterfaces = MyTestClass.class.getGenericInterfaces();
  10. boolean isClassType = genericInterfaces[0] instanceof Class;
  11. //ParameterizedType所代表的泛型信息后文在介绍,现阶段只需要知道它存储了泛型信息
  12. boolean isGenericType = genericInterfaces[0] instanceof ParameterizedType;
  13. //true
  14. System.out.println("isClass:" + isClassType);
  15. //false
  16. System.out.println("isGenericType:" + isGenericType);
  17. }
  18. }

上面的案例接口并没有泛型信息,我们添加一个小的泛型看看

  1. interface GeneircInteface<T> {
  2. }
  3. class MyTestClass implements GeneircInteface<String> {
  4. }
  5. public class Java {
  6. public static void main(String[] args) {
  7. Type[] genericInterfaces = MyTestClass.class.getGenericInterfaces();
  8. boolean isClassType = genericInterfaces[0] instanceof Class;
  9. boolean isGenericType = genericInterfaces[0] instanceof ParameterizedType;
  10. //false
  11. System.out.println("isClass:" + isClassType);
  12. //true
  13. System.out.println("isGenericType:" + isGenericType);
  14. }
  15. }

ParameterizedType

  1. //ParameterizedType.java
  2. /**
  3. * 表示一个泛型声明类型:
  4. * 比如
  5. * class MyTestClass {
  6. * class GeneircInteface<A,B> {
  7. *
  8. * }
  9. * }
  10. *
  11. *
  12. * 我们获取类class GeneircInteface<A,B>的ParameterizedType
  13. * 可通过ParameterizedType可以获得泛型所在的类GeneircInteface,对应getRawType函数
  14. * 可通过ParameterizedType泛型数组A,B被type表示的数组,对应getActualTypeArguments函数
  15. * 可通过ParameterizedType获取泛型类所在父类,这里返回MyTestClass 对应getOwnerType函数
  16. **/
  17. public interface ParameterizedType extends Type {
  18. /**
  19. * 返回所声明的泛型列表.
  20. * eg.
  21. * interface GeneircInteface<A,B> {}
  22. * 返回对应A和B的type数组
  23. */
  24. Type[] getActualTypeArguments();
  25. /**
  26. * 泛型所在的类
  27. * 如interface GeneircInteface<A,B> {}
  28. * 返回 GeneircInteface.这里只能返回class类型的type
  29. */
  30. Type getRawType();
  31. /**
  32. * 返回泛型所在类的嵌套父类
  33. * class MyTestClass {
  34. * class GeneircInteface<A,B> {
  35. *
  36. * }
  37. * }
  38. * 我们获取类class GeneircInteface<A,B>的ParameterizedType
  39. * getOwnerType返回MyTestClass .这里只能返回class类型的type
  40. **/
  41. Type getOwnerType();
  42. }

案例:

  1. package main;
  2. class MyTestClass {
  3. static class GeneircInteface<A,B> {
  4. }
  5. }
  6. public class Java {
  7. public static void main(String[] args) throws NoSuchMethodException {
  8. Method method = new Java().getClass().getMethod("applyMethod", MyTestClass.GeneircInteface.class);
  9. Type[] types = method.getGenericParameterTypes();
  10. ParameterizedType pType = (ParameterizedType)types[0];
  11. //返回所有者类型,打印结果是
  12. //输出 class Main.MyTestClass
  13. System.out.println(pType.getOwnerType());
  14. //这里只能是Class类的Type哦,
  15. System.out.println(pType.getOwnerType() instanceof Class);
  16. //因为泛型有两个,所以数组大小应该是2
  17. //返回的Type又是哪种这里不在铺开
  18. Type[] actualTypeArguments = pType.getActualTypeArguments();
  19. //输出2
  20. System.out.println(actualTypeArguments.length);
  21. //返回GeneircInteface的class,这里不在是ParameterizedType
  22. //Class 也是Type子类型哦
  23. //输出true
  24. System.out.println(pType.getRawType() instanceof Class);
  25. }
  26. public static <T,C> void applyMethod(MyTestClass.GeneircInteface<T,C> list){
  27. }
  28. }

这个案例参考ParameterizedType.getOwnerType() 函数怎么用?

GenericArrayType

数组类型的泛型Type.

  1. //GenericArrayType.java
  2. /**
  3. * 表示一个数组存储的类型是包含泛型声明的类.
  4. * eg.
  5. * class GeneircInteface<A,B> {}
  6. * 一个数组:
  7. * GeneircInteface<String,String> [] arr;
  8. * GenericArrayType所代表的就这个数组泛型信息
  9. **/
  10. public interface GenericArrayType extends Type {
  11. //数组存储的泛型类的Type
  12. //比如 GeneircInteface<String,String> [] arr;返回GeneircInteface的type
  13. Type getGenericComponentType();
  14. }
  15. package main;
  16. class MyTestClass {
  17. static class GeneircInteface<A,B> {
  18. }
  19. }
  20. public class Java {
  21. //声明泛型具体类型
  22. MyTestClass.GeneircInteface<Object, String>[] arr;
  23. //没有声明泛型具体类型
  24. MyTestClass.GeneircInteface[] arr2;
  25. List<Integer>[] arr3;
  26. public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
  27. Field arr = Java.class.getDeclaredField("arr");
  28. Field arr2 = Java.class.getDeclaredField("arr2");
  29. Field arr3 = Java.class.getDeclaredField("arr3");
  30. Type genericType = arr.getGenericType();
  31. Type genericType2 = arr2.getGenericType();
  32. Type genericType3 = arr3.getGenericType();
  33. //输出true
  34. System.out.println(genericType instanceof GenericArrayType);
  35. //输出false 这里主要提醒大家这个细节,如果没有声明泛型那么返回的type是class
  36. System.out.println(genericType2 instanceof GenericArrayType);
  37. //输出true
  38. System.out.println(genericType2 instanceof Class);
  39. //输出true
  40. System.out.println(genericType3 instanceof GenericArrayType);
  41. GenericArrayType genericTypeObj = (GenericArrayType) genericType;
  42. //这里返回
  43. Type genericComponentType = genericTypeObj.getGenericComponentType();
  44. //输出true ,上文讲过ParameterizedType这里就不在铺开
  45. //返回的是GeneircInteface的ParameterizedType
  46. System.out.println(genericComponentType instanceof ParameterizedType);
  47. GenericArrayType genericTypeObj3 = (GenericArrayType) genericType;
  48. //返回true,这里同上
  49. System.out.println(genericTypeObj3 instanceof ParameterizedType);
  50. }
  51. }

TypeVariable

  1. //表示一个类的原始泛型声明,这里必须区分ParameterizedType的区别
  2. //ParameterizedType用于获取已经明确泛型的类等,否则无法获取,比如interface MyInt< k extends String>就无法获取
  3. // TypeVariable 可以获取interface MyInt< k extends String>这里泛型信息,包括泛型的名字k和上界string
  4. public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
  5. // 返回泛型所定义的上界
  6. // interface MyInt< k extends String>
  7. // 返回的就是String.
  8. // 为什么定义成数组?也许是向前兼容??
  9. Type[] getBounds();
  10. //定义泛型所在的类
  11. //interface MyInt< k extends String>
  12. //这里返回MyInt对应的class
  13. D getGenericDeclaration();
  14. //返回泛型最原始定义的名字
  15. //
  16. // interface MyInt< k extends String>
  17. // 这里就是返回 K
  18. //
  19. String getName();
  20. //1.8多出的api,但是我没理解所以不讲解
  21. AnnotatedType[] getAnnotatedBounds();
  22. }
  23. public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
  24. TypeVariable<Class<MyTestClass.GeneircInteface>>[] typeParameters = MyTestClass.GeneircInteface.class.getTypeParameters();
  25. //因为泛型有两个所以这里返回2
  26. System.out.println(typeParameters.length);
  27. TypeVariable<Class<MyTestClass.GeneircInteface>> typeParameter_0 = typeParameters[0];
  28. TypeVariable<Class<MyTestClass.GeneircInteface>> typeParameter_1 = typeParameters[1];
  29. //获得声明泛型的字母
  30. //输出k
  31. System.out.println(typeParameter_0.getName());
  32. //输出B
  33. System.out.println(typeParameter_1.getName());
  34. Type[] bounds = typeParameter_0.getBounds();
  35. //长度为1,我不理解为什么这里要返回数组类型,难不成可以定义多个上届
  36. System.out.println(bounds.length);
  37. Type bound = bounds[0];
  38. //输出上届class java.lang.String
  39. System.out.println(bound);
  40. //true
  41. System.out.println(bound instanceof Class);
  42. //输出class java.lang.Class
  43. System.out.println(typeParameter_0.getGenericDeclaration());
  44. }

WildcardType

  1. //WildcardType.java
  2. //上文介绍的几个Type在大多数情况可以得到足够的泛型信息,但是少了一种就是泛型的上下界信息
  3. //定义上界:List<? extends String>
  4. //定义下界:List<? super String> listOne
  5. public interface WildcardType extends Type {
  6. //获取泛型的上界.如果没有设置那么就是Object,也就是说数组长度一定为1
  7. //为什么是数组类型,也许为了向前兼容
  8. Type[] getUpperBounds();
  9. //获取泛型的下界.如果没有设置那么就是长度为0的数组
  10. //为什么是数组类型,也许为了向前兼容
  11. Type[] getLowerBounds();
  12. }
  13. public class Java {
  14. public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
  15. Method testFunction = Java.class.getDeclaredMethod("testFunction", List.class, List.class);
  16. //因为参数有两个所以这里数组长度为2
  17. Type[] genericParameterTypes = testFunction.getGenericParameterTypes();
  18. //输出2
  19. System.out.println(genericParameterTypes.length);
  20. //因为参数包含所以必然是一个ParameterizedType
  21. ParameterizedType genericParameterType = (ParameterizedType) genericParameterTypes[0];
  22. //通过ParameterizedType的getActualTypeArguments返回具体泛型信息
  23. Type[] actualTypeArguments = genericParameterType.getActualTypeArguments();
  24. //因为这里只有一个泛型<? super String> 所以只数组长度1
  25. //输出1
  26. System.out.println(actualTypeArguments.length);
  27. //<? super String> 包含通配符所以type必然是WildcardType
  28. WildcardType actualTypeArgument = (WildcardType) actualTypeArguments[0];
  29. //输出? super java.lang.String
  30. System.out.println(actualTypeArgument);
  31. //<? super String>声明了一个下界,所以lowerBounds不为空,长度为1
  32. //为什么是数组而不是直接一个Type我也没确定,也许是向前兼容
  33. Type[] lowerBounds = actualTypeArgument.getLowerBounds();
  34. //输出1
  35. System.out.println(lowerBounds.length);
  36. //下界是String且不不包含泛型信息,可以输出Type为class
  37. Type lowerBound = lowerBounds[0];
  38. //输出
  39. System.out.println(lowerBound instanceof Class);
  40. //获取上届信息,没有声明默认就是Object
  41. Type[] upperBounds = actualTypeArgument.getUpperBounds();
  42. //输出1
  43. System.out.println(upperBounds.length);
  44. //输出 class java.lang.Object
  45. System.out.println(upperBounds[0]);
  46. //输出 true
  47. System.out.println(upperBounds[0] instanceof Class);
  48. //关于的例子 List<? extends String> listTwo
  49. ParameterizedType genericParameterTypeTwo = (ParameterizedType) genericParameterTypes[1];
  50. Type[] actualTypeArgumentsTwo = genericParameterTypeTwo.getActualTypeArguments();
  51. WildcardType wildcardTypeTwo = (WildcardType) actualTypeArgumentsTwo[0];
  52. //注意List<? extends String>定义了上届,但是下界没有定义所以这里输出0
  53. //输出 0
  54. System.out.println(wildcardTypeTwo.getLowerBounds().length);
  55. //定义了上界所以这里长度为1
  56. //输出1
  57. System.out.println(wildcardTypeTwo.getUpperBounds().length);
  58. }
  59. void testFunction(List<? super String> listOne, List<? extends String> listTwo) {
  60. }
  61. }

参考

Java中的Type
Java中的Type类型详解
ParameterizedType.getOwnerType() 函数怎么用?

发表评论

表情:
评论列表 (有 0 条评论,314人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java

    前言 本文讲从字节码层面深度学习泛型,这也是大厂常见面试 我们定义了如下类图所示类: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5

    相关 Java-类型

    Java泛型-类型擦除 一、概述 Java泛型在使用过程有诸多的问题,如不存在List.class, List不能赋值给List(不可协变),奇怪的ClassCastE