Java泛型的反射问题分析

Wesley13
• 阅读 563

背景和问题

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

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

代码:

interface IStudent {
    void learn();
}

interface ISchool<R extends IStudent> {
    void add(R r);
}

class MiddleStudent implements IStudent{
    [@Override](https://my.oschina.net/u/1162528)
    public void learn() {
    //TODO
    }
}

class MiddleSchool implements ISchool<MiddleStudent> {
    [@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.<init>()
    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<String> listString = new ArrayList<String>();
        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之前的字节码就不兼容了。

知识点:

  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
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
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
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java 泛型详解
对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下。本文参考java泛型详解、Java中的泛型方法、java泛型详解1\.概述泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。什么是泛型?为什么要使用泛型?泛型,即“参数化类型”。一提到参数,最熟
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Thinking in java Chapter15 泛型
1与C比较2简单泛型泛型类3泛型接口4泛型方法5匿名内部类6构建复杂模型78910“泛型”意思就是:适用于许多许多的类型<h2id"1"1与C比较</h2C
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了