Java泛型的反射问题分析

背景和问题

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

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

代码:

1interface IStudent { 2 void learn(); 3} 4 5interface ISchool<R extends IStudent> { 6 void add(R r); 7} 8 9class MiddleStudent implements IStudent{ 10 [@Override](https://my.oschina.net/u/1162528) 11 public void learn() { 12 //TODO 13 } 14} 15 16class MiddleSchool implements ISchool<MiddleStudent> { 17 [@Override](https://my.oschina.net/u/1162528) 18 public void add(MiddleStudent middleStudent) { 19 /TODO 20 } 21} 22public class Test { 23 public static void main(String[] args) throws IllegalAccessException, InstantiationException { 24 //所有方法 25 Method[] methods = MiddleSchool.class.getMethods(); 26 for (Method method : methods) { 27 System.out.println(method.getName()); 28 //找add方法 29 if("add".equals(method.getName())){ 30 //取第一个参数的class 31 Class clazz = method.getParameterTypes()[0]; 32 //创建实例 33 clazz.newInstance(); 34 } 35 } 36 } 37}

异常

1Exception in thread "main" java.lang.InstantiationException: IStudent 2 at java.lang.Class.newInstance(Class.java:427) 3 at Test.main(Test.java:44) 4 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 5 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 6 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 7 at java.lang.reflect.Method.invoke(Method.java:497) 8 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) 9Caused by: java.lang.NoSuchMethodException: IStudent.<init>() 10 at java.lang.Class.getConstructor0(Class.java:3082) 11 at java.lang.Class.newInstance(Class.java:412) 12 ... 6 more

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

1//判断class类型是接口,返回 2if(clazz.isInterface()){ 3 continue; 4}

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

javap -verbose MiddleSchool.class

1public void add(MiddleStudent); 2 descriptor: (LMiddleStudent;)V 3 flags: ACC_PUBLIC 4 Code: 5 stack=0, locals=2, args_size=2 6 0: return 7 LineNumberTable: 8 line 29: 0 9 LocalVariableTable: 10 Start Length Slot Name Signature 11 0 1 0 this LMiddleSchool; 12 0 1 1 middleStudent LMiddleStudent; 13 14 public void add(IStudent); 15 descriptor: (LIStudent;)V 16 flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC 17 Code: 18 stack=2, locals=2, args_size=2 19 0: aload_0 20 1: aload_1 21 2: checkcast #2 // class MiddleStudent 22 5: invokevirtual #3 // Method add:(LMiddleStudent;)V 23 8: return 24 LineNumberTable: 25 line 24: 0 26 LocalVariableTable: 27 Start Length Slot Name Signature 28 0 9 0 this LMiddleSchool;

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

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

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

1 public static void main(String[] args) { 2 //明确泛型 3 List<String> listString = new ArrayList<String>(); 4 listString.add("abc"); 5 //不明确泛型 6 List list = new ArrayList<>(); 7 list.add(new Object()); 8 //结果为true,可见class是同一个 9 System.out.println(listString.getClass() == list.getClass()); 10 }

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

知识点:

  1. 泛型类型擦除 type erasure
  2. 桥接方法

感谢前人栽树

  1. https://www.ibm.com/developerworks/cn/java/j-jtp01255.html
  2. https://rednaxelafx.iteye.com/blog/586212
  3. https://blog.csdn.net/mhmyqn/article/details/47342577
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Java日期时间API系列31

  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前