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_info
的attribute
中.
所以我们可以通过反射获取即可
具体可以看网上的其他文章https://www.cnblogs.com/wwjj4811/p/12592443.html
泛型函数会比其他不含泛型的函数额外多一个signature
类型的attribute_info
属性存在medhod_info
中
这里解释下descriptor
和signature
的区别:
如果你在编写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() 函数怎么用?
还没有评论,来说两句吧...