java中泛型接口(类)实现类中的桥接方法

桥接方法的英文说明:https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html。总结起来大概是:因为java代码编译后都会泛型擦除,所以泛型方法中的泛型参数和泛型返回值在class文件中都是object。接口或者类中泛型方法的签名会变成参数为object,而实现类中的方法签名中参数是具体的方法。对泛型方法的重写更像是重载。简单翻译下上面英文,再说说因为这个问题导致的一个线上问题。

如下两个类:

public class Node<T> {
    public T data;
    public Node(T data) { this.data = data; }
    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

泛型擦除编译后的代码为:

public class Node {
    public Object data;
    public Node(Object data) { this.data = data; }
    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

执行下面代码,在第三行会抛出类型强转错误。

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     // Causes a ClassCastException to be thrown.
Integer x = mn.data;    

因为根据方法签名,子类MyNode中的setData方法并没有重新父类的setData方法,为了保持泛型在类型擦除后的多态性,java编译器会在子类中添加一个桥接方法,如下。因为上面代码段第三行中的n是MyNode类型,所有调用的是MyNode类中的Java编译器生成的setData方法,也就是桥接方法。这个方法会进行类型强转然后调用子类泛型具体化的setData方法,强转时抛了类型强转异常。

class MyNode extends Node {
    // Bridge method generated by the compiler
    public void setData(Object data) {
        setData((Integer) data);
    }
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

在java代码开发工具及编译器中,是调用不到一个泛型实现类中的桥接方法(idea中不会显示mn中有一个setData(Objecet data)方法可以调用,直接调用也编译器也会报错)。但是通过class的getDeclaredMethods()方法可以拿到桥接方法,并且子类具体化方法上的注解桥接方法上也存在,也就是说如果在MyNode中setData(Integer data)方法上添加方法注解,那么MyNode中的桥接方法setData(Objecet data)上也有这个注解。我们线上问题就出现在这。

我们通过netty建立长连接,在客户端会根据方法上的注解(自己定义的注解,注解中有唯一标识方法的参数tpye)拿到方法和方法所属的对象和参数,将方法及对象、参数封装成对象targetMethod,以type为key,targetMethod为value进行存储。这个过程是spring容器启动时候做的。我们在调用时候就会偶发性的遇到ClassCastException异常。但在工程启动到工程结束的整个过程中表现一致,也就是第一次调用抛异常后续调用就会抛异常,直到重启才有可能正常调用。这就是因为在spring启动时我们在map中存储的可能是这个泛型实现类中的桥接方法,桥接方法参数是object,我们用json将传入的字符串转换为参数时,转换的也是object,然后桥接方法进行类型强转调用子类具体化方法时报错。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
的产生条件 是为了解决在编译期间无法确定类而引入的,其产生条件包括: 1. 在代码需要使用到,但是这个的具体类在编译期间是不确定的; 2. 在代码需要对进行操作,比如调用其方法或者获取其属性值。 的概念 是指在编写代码时,不需要指定数据类,而是在使用时再指定具体的数据类。这样就可以提高代码的复用性和灵活性。 类是指使用定义的类,其参数可以用在类的成员变量、成员方法、构造方法类派生类 类派生类是指使用定义的类的子类,其子类可以继续使用父类定义的。 带子类 带子类是指在继承类时,子类也要使用。 不带子类 不带子类是指在继承类时,子类不使用接口 接口是指使用定义的接口,其参数可以用在接口方法方法 方法是指使用定义的方法,其参数可以用在方法的参数列表、返回值、方法。 类通配符 类通配符是指在定义时使用的一种特殊符号,用于表示不确定的类。 引出类通配符 类通配符可以用于引出参数的上限或者下限。 类通配符的上限 类通配符的上限是指使用 extends 关键字限制参数的范围,表示参数必须是某个类的子类或者实现类。 类通配符的下限 类通配符的下限是指使用 super 关键字限制参数的范围,表示参数必须是某个类的父类或者超类。 类擦除 类擦除是指在编译期间,将参数替换为其上限或者 Object 类的过程。 无限制类的擦除 无限制类的擦除是指在参数没有明确指定上限或者下限时,将其擦除为 Object 类。 有限制类擦除 有限制类擦除是指在参数有明确指定上限或者下限时,将其擦除为上限或者下限。 擦除方法定义的参数 在方法,如果定义了参数,则在编译期间也会进行类擦除。 方法类或者接口,如果有方法,则在编译期间会自动生成方法来确保类安全。 数组 数组是指使用定义的数组,其数组元素的类参数。 与反射 与反射的结合可以实现动态创建对象、获取信息等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值