理解Java泛型中的桥方法

Java泛型中存在类型擦除机制,也就是说泛型在编译期间进行类型检验上做到有效安全,但是在运行当中,会将该泛型类型用限定类型(无限定的类型变量则用Object代替)代替,这种类型擦除带来了两个复杂的问题。
 
看下面代码示例:

public class A<T> {
    private T value;
    public T getValue() {
        return value;
    }
    public void setValue(T value) {
        this.value = value;
    }
}

//编译时会进行类型擦除
public class A {
    private Object value;
    public Object getValue() {
        return value;
    }
    public void setValue(Object value) {
        this.value = value;
    }
}

可以看出,泛型类型T在编译后变成了Object,虚拟机中是没有泛型的。

接着看下面这段代码:

public class B extends A<String> {
    @Override
    public void setValue(String value) {
    }
}

我们的意图是:新写了一个 B 类,继承自 A 类,并重写A 类的 setValue 方法。

但问题出来了,我们发现 B 类中的 setValue 方法参数与 A 类中的 setValue 方法参数不一样。按照 Java 重写方法的规则,B 类中的 setValue 方法实际上并没有重写父类中的方法,而是重载。也就是说我们的意图是重写,而实际上却是重载,类型擦除与多态发生了冲突。

Java如何解决这个问题? 实际上,Java编译器会在 B 类中自动生成一个桥方法(bridge method)。
来看下B类的字节码:

// class version 51.0 (51)
// access flags 0x21
// signature Lcom/example/applaunch/A<Ljava/lang/String;>;
// declaration: com/example/applaunch/B extends com.example.applaunch.A<java.lang.String>
public class com/example/applaunch/B extends com/example/applaunch/A  {

  // compiled from: B.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL com/example/applaunch/A.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/applaunch/B; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public setValue(Ljava/lang/String;)V
   L0
    LINENUMBER 6 L0
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/applaunch/B; L0 L1 0
    LOCALVARIABLE value Ljava/lang/String; L0 L1 1
    MAXSTACK = 0
    MAXLOCALS = 2

  // access flags 0x1041
  public synthetic bridge setValue(Ljava/lang/Object;)V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    ALOAD 1
    CHECKCAST java/lang/String
    INVOKEVIRTUAL com/example/applaunch/B.setValue (Ljava/lang/String;)V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/applaunch/B; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 2
}


可以看到子类B中有两个setValue方法:

public void setValue(String value)
{
	...
}
public void setValue(Object value){
    setValue((String)value);
}

一个参数为 String 类型,一个参数为 Object 类型,参数为 Object 类型的就是 Java 编译器帮我们生成的桥方法,桥方法内部其实就是调用了子类新定义的 setValue 方法,这样就避免了子类重写了父类的方法后还能调用到父类的方法。

同样的,如果给B类添加一个getValue方法,编译器也会自动生成一个桥方法:

public class B extends A {

    public String getValue(){...}

	//编译器自动生成的桥方法
    public Object getValue(){
        return getValue();
    }
}

在java代码中,仅返回类型不同的两个方法是不合法的。但是,在虚拟机中,用参数类型和返回类型确定一个方法。因此, 编译器可能产生两个仅返回类型不同的方法的字节码,虚拟机能够正确地处理这一情况。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值