JAVA 泛型 反射 获取 为什么_Java泛型的反射问题分析

背景和问题

我们需要通过方法的参数类型,创建参数的实例。本地开发测试正常,部署测试环境提示反射异常。

为便于理解,改为学生与学校的关系表示。

代码:

interface IStudent {

void learn();

}

interface ISchool {

void add(R r);

}

class MiddleStudent implements IStudent{

[@Override](https://my.oschina.net/u/1162528)

public void learn() {

//TODO

}

}

class MiddleSchool implements ISchool {

[@Override](https://my.oschina.net/u/1162528)

public void add(MiddleStudent middleStudent) {

/TODO

}

}

public class Test {

public static void main(String[] args) throws IllegalAccessException, InstantiationException {

//所有方法

Method[] methods = MiddleSchool.class.getMethods();

for (Method method : methods) {

System.out.println(method.getName());

//找add方法

if("add".equals(method.getName())){

//取第一个参数的class

Class clazz = method.getParameterTypes()[0];

//创建实例

clazz.newInstance();

}

}

}

}

异常

Exception in thread "main" java.lang.InstantiationException: IStudent

at java.lang.Class.newInstance(Class.java:427)

at Test.main(Test.java:44)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Caused by: java.lang.NoSuchMethodException: IStudent.()

at java.lang.Class.getConstructor0(Class.java:3082)

at java.lang.Class.newInstance(Class.java:412)

... 6 more

通过debug,发现add同名方法有两个,参数分别是MiddleStudent和IStudent,而IStudent是接口,不能实例化。解决方式:

//判断class类型是接口,返回

if(clazz.isInterface()){

continue;

}

为什么会有两个add方法呢?

javap -verbose MiddleSchool.class

public void add(MiddleStudent);

descriptor: (LMiddleStudent;)V

flags: ACC_PUBLIC

Code:

stack=0, locals=2, args_size=2

0: return

LineNumberTable:

line 29: 0

LocalVariableTable:

Start Length Slot Name Signature

0 1 0 this LMiddleSchool;

0 1 1 middleStudent LMiddleStudent;

public void add(IStudent);

descriptor: (LIStudent;)V

flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC

Code:

stack=2, locals=2, args_size=2

0: aload_0

1: aload_1

2: checkcast #2 // class MiddleStudent

5: invokevirtual #3 // Method add:(LMiddleStudent;)V

8: return

LineNumberTable:

line 24: 0

LocalVariableTable:

Start Length Slot Name Signature

0 9 0 this LMiddleSchool;

编译后已经是两个add方法了,其中add(IStudent)的flags值ACC_BRIDGE,表示桥接方法(桥接方法实际是调用了实际的泛型方法)

为什么要生成桥接方法呢?

对于明确或不明确的泛型类,保证兼容性。如List类:

public static void main(String[] args) {

//明确泛型

List listString = new ArrayList();

listString.add("abc");

//不明确泛型

List list = new ArrayList<>();

list.add(new Object());

//结果为true,可见class是同一个

System.out.println(listString.getClass() == list.getClass());

}

泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型,如果不正确会在编译时就会发现错误,而不必等到运行时才发现错误。因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),但是我们还是可以通过反射API来获取泛型的信息,在编译时可以通过泛型来保证类型的正确性,而不必等到运行时才发现类型不正确。由于java泛型的擦除特性,如果不生成桥接方法,那么与1.5之前的字节码就不兼容了。

知识点:

泛型类型擦除 type erasure

桥接方法

感谢前人栽树

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值