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
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值