java 泛型擦除

前言

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

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

泛型的协变与逆变

定义
在这里插入图片描述

泛型 extend的作用

请先浏览如下代码

 	        List<? extends Female> extenfemalesList1 = new ArrayList<Female>();
//        List<? extends Female> extenfemalesList2 = new ArrayList<Human>();//error
//        List<? extends Female> extenfemalesList3 = new ArrayList<Object>();//error
        List<? extends Female> extenfemalesList4 = new ArrayList<FemaleTeacher>();
        Female female = extenfemalesList1.get(0);
//        extenfemalesList1.add(new Female()); //erro
复制代码

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

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

   Female female = extenfemalesList1.get(0);
复制代码

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

 extenfemalesList1.add(new Female());
复制代码

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

泛型 super的作用

    List<? super Female> extenfemalesList1 = new ArrayList<Female>();
        List<? super Female> extenfemalesList2 = new ArrayList<Human>();
        List<? super Female> extenfemalesList3 = new ArrayList<Object>();
//        List<? super Female> extenfemalesList4 = new ArrayList<FemaleTeacher>();//erro
//        Female female = extenfemalesList1.get(0);//erro
        extenfemalesList1.add(new Female()); 
        Object female = extenfemalesList1.get(0);
复制代码

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

所以以下代码顺利编译

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

违背语义报错代码

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

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

泛型擦除

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

泛型函数

 static  void test(List<FemaleTeacher> listParameter) {
        FemaleTeacher femaleTeacher = listParameter.get(0);
        System.out.println(femaleTeacher);
    }
复制代码

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

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

   static  void test(List<Object> listParameter) {
        Object femaleTeacher = listParameter.get(0);
        FemaleTeacher femaleTeacher1 = (FemaleTeacher) femaleTeacher;
        System.out.println(femaleTeacher1);

    }
复制代码

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

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

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

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

泛型类

public class MyClassA<T> {


    public void say(T t) {
        System.out.println(t);
    }

  
}

public class MyClassB extends MyClassA<Human> {


}
复制代码

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

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

https://www.cnblogs.com/one777/p/7833789.html
https://blog.csdn.net/u011082160/article/details/101025884
复制代码

type

预备知识

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

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

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


interface GeneircInteface {}
class MyTestClass implements GeneircInteface {}

public class Java {
    public static void main(String[] args) {
    	//getGenericInterfaces获取接口上的泛型信息,如果没有实现接口返回的数组为0
    	//如果想得到继承类的泛型信息可以用getGenericSuperclass
        Type[] genericInterfaces = MyTestClass.class.getGenericInterfaces();
        boolean isClassType = genericInterfaces[0] instanceof Class;
        //ParameterizedType所代表的泛型信息后文在介绍,现阶段只需要知道它存储了泛型信息
        boolean isGenericType = genericInterfaces[0] instanceof ParameterizedType;
        //true
        System.out.println("isClass:" + isClassType);
        //false
        System.out.println("isGenericType:" + isGenericType);
    }
}
复制代码

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


interface GeneircInteface<T> {}
class MyTestClass implements GeneircInteface<String> {}

public class Java {
    public static void main(String[] args) {
        Type[] genericInterfaces = MyTestClass.class.getGenericInterfaces();
        boolean isClassType = genericInterfaces[0] instanceof Class;
        boolean isGenericType = genericInterfaces[0] instanceof ParameterizedType;
        //false
        System.out.println("isClass:" + isClassType);
        //true
        System.out.println("isGenericType:" + isGenericType);
    }
}
复制代码

ParameterizedType

//ParameterizedType.java
/**
 * 表示一个泛型声明类型:
 * 比如
 *    class MyTestClass {
 *   			class GeneircInteface<A,B> {
 *
 *   			}
 *    } 
 *  
 *  
 *  我们获取类class GeneircInteface<A,B>的ParameterizedType
 * 可通过ParameterizedType可以获得泛型所在的类GeneircInteface,对应getRawType函数
 * 可通过ParameterizedType泛型数组A,B被type表示的数组,对应getActualTypeArguments函数
 * 可通过ParameterizedType获取泛型类所在父类,这里返回MyTestClass 对应getOwnerType函数
 **/
public interface ParameterizedType extends Type {
    /**
     * 返回所声明的泛型列表.
     * eg.
     * interface GeneircInteface<A,B> {}
     * 返回对应A和B的type数组
     */
    Type[] getActualTypeArguments();

    /**
     * 泛型所在的类 
     * 如interface GeneircInteface<A,B> {}
     * 返回 GeneircInteface.这里只能返回class类型的type
     */
    Type getRawType();

   	/**
   	* 返回泛型所在类的嵌套父类
   	*    class MyTestClass {
    *   			class GeneircInteface<A,B> {
    *
    *   			}
    *    } 
    *  我们获取类class GeneircInteface<A,B>的ParameterizedType
    *  getOwnerType返回MyTestClass .这里只能返回class类型的type
   	**/
    Type getOwnerType();
}
复制代码

案例:

package main;
class MyTestClass {
   static class GeneircInteface<A,B> {

    }
}
复制代码
public class Java {

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = new Java().getClass().getMethod("applyMethod", MyTestClass.GeneircInteface.class);
        Type[] types = method.getGenericParameterTypes();
        ParameterizedType pType = (ParameterizedType)types[0];
        //返回所有者类型,打印结果是
        //输出 class Main.MyTestClass
        System.out.println(pType.getOwnerType());
        //这里只能是Class类的Type哦,
        System.out.println(pType.getOwnerType() instanceof  Class);


        //因为泛型有两个,所以数组大小应该是2
        //返回的Type又是哪种这里不在铺开
        Type[] actualTypeArguments = pType.getActualTypeArguments();

        //输出2
        System.out.println(actualTypeArguments.length);
        
        //返回GeneircInteface的class,这里不在是ParameterizedType
        //Class 也是Type子类型哦
        //输出true
        System.out.println(pType.getRawType() instanceof  Class);
    }
    public static <T,C> void applyMethod(MyTestClass.GeneircInteface<T,C> list){}
    
}
复制代码

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

GenericArrayType

数组类型的泛型Type.

//GenericArrayType.java
/**
* 表示一个数组存储的类型是包含泛型声明的类.
* eg.
*  class GeneircInteface<A,B> {}
*  一个数组:
*  GeneircInteface<String,String> [] arr;
*  GenericArrayType所代表的就这个数组泛型信息
**/
public interface GenericArrayType extends Type {
 	//数组存储的泛型类的Type
 	//比如 GeneircInteface<String,String> [] arr;返回GeneircInteface的type
    Type getGenericComponentType();
}
复制代码
package main;
class MyTestClass {
   static class GeneircInteface<A,B> {

    }
}
复制代码

public class Java {

    //声明泛型具体类型
    MyTestClass.GeneircInteface<Object, String>[] arr;
    //没有声明泛型具体类型
    MyTestClass.GeneircInteface[] arr2;

    List<Integer>[] arr3;


    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
        Field arr = Java.class.getDeclaredField("arr");
        Field arr2 = Java.class.getDeclaredField("arr2");
        Field arr3 = Java.class.getDeclaredField("arr3");

        Type genericType = arr.getGenericType();
        Type genericType2 = arr2.getGenericType();
        Type genericType3 = arr3.getGenericType();
        //输出true
        System.out.println(genericType instanceof GenericArrayType);
        //输出false 这里主要提醒大家这个细节,如果没有声明泛型那么返回的type是class
        System.out.println(genericType2 instanceof GenericArrayType);
        //输出true
        System.out.println(genericType2 instanceof Class);
        //输出true
        System.out.println(genericType3 instanceof GenericArrayType);

        GenericArrayType genericTypeObj = (GenericArrayType) genericType;
        //这里返回
        Type genericComponentType = genericTypeObj.getGenericComponentType();

        //输出true ,上文讲过ParameterizedType这里就不在铺开
        //返回的是GeneircInteface的ParameterizedType
        System.out.println(genericComponentType instanceof ParameterizedType);


        GenericArrayType genericTypeObj3 = (GenericArrayType) genericType;
        //返回true,这里同上
        System.out.println(genericTypeObj3 instanceof ParameterizedType);

    }
    
}
复制代码

TypeVariable

//表示一个类的原始泛型声明,这里必须区分ParameterizedType的区别
//ParameterizedType用于获取已经明确泛型的类等,否则无法获取,比如interface MyInt< k extends String>就无法获取
// TypeVariable 可以获取interface MyInt< k extends String>这里泛型信息,包括泛型的名字k和上界string
public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
  
    // 返回泛型所定义的上界
  	// interface MyInt< k extends String>
  	// 返回的就是String.
  	// 为什么定义成数组?也许是向前兼容??
    Type[] getBounds();

    //定义泛型所在的类
    //interface MyInt< k extends String>
    //这里返回MyInt对应的class
    D getGenericDeclaration();
	//返回泛型最原始定义的名字
	//
	// interface MyInt< k extends String>
	// 这里就是返回 K
	// 
    String getName();

  	//1.8多出的api,但是我没理解所以不讲解
    AnnotatedType[] getAnnotatedBounds();
}
复制代码
 public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {

        TypeVariable<Class<MyTestClass.GeneircInteface>>[] typeParameters = MyTestClass.GeneircInteface.class.getTypeParameters();
        //因为泛型有两个所以这里返回2
        System.out.println(typeParameters.length);

        TypeVariable<Class<MyTestClass.GeneircInteface>> typeParameter_0 = typeParameters[0];

        TypeVariable<Class<MyTestClass.GeneircInteface>> typeParameter_1 = typeParameters[1];

        //获得声明泛型的字母
        //输出k
        System.out.println(typeParameter_0.getName());
        //输出B
        System.out.println(typeParameter_1.getName());

        Type[] bounds = typeParameter_0.getBounds();
        //长度为1,我不理解为什么这里要返回数组类型,难不成可以定义多个上届
        System.out.println(bounds.length);

        Type bound = bounds[0];
        //输出上届class java.lang.String
        System.out.println(bound);
        //true
        System.out.println(bound instanceof  Class);

        //输出class java.lang.Class
        System.out.println(typeParameter_0.getGenericDeclaration());

    }
复制代码

WildcardType

//WildcardType.java
//上文介绍的几个Type在大多数情况可以得到足够的泛型信息,但是少了一种就是泛型的上下界信息
//定义上界:List<? extends String>
//定义下界:List<? super String> listOne
public interface WildcardType extends Type {
    //获取泛型的上界.如果没有设置那么就是Object,也就是说数组长度一定为1
    //为什么是数组类型,也许为了向前兼容
    Type[] getUpperBounds();
    //获取泛型的下界.如果没有设置那么就是长度为0的数组
    //为什么是数组类型,也许为了向前兼容
    Type[] getLowerBounds();
  
}
复制代码

public class Java {


    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {

        Method testFunction = Java.class.getDeclaredMethod("testFunction", List.class, List.class);

        //因为参数有两个所以这里数组长度为2
        Type[] genericParameterTypes = testFunction.getGenericParameterTypes();
        //输出2
        System.out.println(genericParameterTypes.length);

        //因为参数包含所以必然是一个ParameterizedType
        ParameterizedType genericParameterType = (ParameterizedType) genericParameterTypes[0];
        //通过ParameterizedType的getActualTypeArguments返回具体泛型信息
        Type[] actualTypeArguments = genericParameterType.getActualTypeArguments();
        //因为这里只有一个泛型<? super String> 所以只数组长度1
        //输出1
        System.out.println(actualTypeArguments.length);

        //<? super String> 包含通配符所以type必然是WildcardType
        WildcardType actualTypeArgument = (WildcardType) actualTypeArguments[0];
        //输出? super java.lang.String
        System.out.println(actualTypeArgument);
        //<? super String>声明了一个下界,所以lowerBounds不为空,长度为1
        //为什么是数组而不是直接一个Type我也没确定,也许是向前兼容
        Type[] lowerBounds = actualTypeArgument.getLowerBounds();
        //输出1
        System.out.println(lowerBounds.length);

        //下界是String且不不包含泛型信息,可以输出Type为class
        Type lowerBound = lowerBounds[0];
        //输出
        System.out.println(lowerBound instanceof Class);

        //获取上届信息,没有声明默认就是Object
        Type[] upperBounds = actualTypeArgument.getUpperBounds();
        //输出1
        System.out.println(upperBounds.length);
        //输出 class java.lang.Object
        System.out.println(upperBounds[0]);
        //输出 true
        System.out.println(upperBounds[0] instanceof Class);

        //关于的例子 List<? extends String> listTwo
        ParameterizedType genericParameterTypeTwo = (ParameterizedType) genericParameterTypes[1];
        Type[] actualTypeArgumentsTwo = genericParameterTypeTwo.getActualTypeArguments();

        WildcardType wildcardTypeTwo = (WildcardType) actualTypeArgumentsTwo[0];

        //注意List<? extends String>定义了上届,但是下界没有定义所以这里输出0
        //输出 0
        System.out.println(wildcardTypeTwo.getLowerBounds().length);
        //定义了上界所以这里长度为1
        //输出1
        System.out.println(wildcardTypeTwo.getUpperBounds().length);
    }


    void testFunction(List<? super String> listOne, List<? extends String> listTwo) {

    }


}
复制代码

参考

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享