6

在阅读协变覆盖时,我发现了一个非常奇怪的事实,

协变方法覆盖是使用桥接技术实现的。它还说这个功能是在java5及更高版本中实现的。(我认为这是因为从java5引入的泛型)

它是如何发生的。请帮我举个例子。

4

3 回答 3

8

考虑一个例子:

public interface Shape<T extends Shape<T>> {
    T getType();
    void setType(T type);
}

public class Circle implements Shape<Circle> {
    Circle getType() { }
    void setType(Circle circle) {  }
}

目前看起来还不错。但是,在类型擦除之后,接口失去了它的泛型类型,类型T被替换为上限。所以接口和类看起来像:

public interface Shape {
    Shape getType();
    void setType(Shape type);
}

public class Circle implements Shape {
    Circle getType() { }
    void setType(Circle circle) {  }
}

现在,问题来了。Circleafter erasure 中的方法并不是真正的Shape. 请注意,现在看起来的方法对其采用的参数和返回的值施加了更大的限制。这是因为擦除改变了接口中方法的签名。

为了解决这个问题,编译器为它们添加了桥接方法,它将调用委托给类中的那些实际方法。

因此,该类真正转换为:

public class Circle implements Shape {
    Circle getType() { }
    void setType(Circle circle) {  }

    // Bridge method added by compiler.
    Shape getType() { return getType(); }  // delegate to actual method
    void setType(Shape shape) { setType((Circle)shape); }  // delegate to actual method
}

因此,桥接方法现在是接口中方法的重写版本,它们将调用委托给执行任务的实际方法。

请注意,桥接方法中使用的类型是接口的类型参数的擦除,在这种情况下为Shape


参考:

于 2013-09-06T10:37:30.593 回答
1

这意味着如果您有一个具有协变(更窄)返回类型的方法,编译器将为您创建一个合成桥方法并通过此桥方法调用覆盖方法。返回类型的协变是在 Java 5 中引入的,与泛型同时引入,它是使用桥接方法实现的。

示例 1:即使没有泛型存在,也使用合成桥方法实现返回类型的协变。

例如:

abstract class A {     

   public abstract A get();
}

class B extends A {

  @Override
  public B get() {
     return this;
  }
}

编译器将使用委托给原始方法的合成桥方法在字节码中实现。原则上,您可以想象编译器将协变覆盖转换为:

abstract class A {     

   public abstract A get();
}

class B extends A {

  //bytecode only bridge method
  @Override
  public A get() {
     return get;
  } 

  public B get() {
     return this;
  }
}

示例 2 - 泛型:让我们看一个涉及泛型的示例。

abstract class A<T> {     

   public abstract T get();
}

class B extends A<String> {

  @Override
  public String get() {
     return "hello";
  }
}

class的方法在返回类型上与get()class中的方法是协变的。在编译这段代码时,编译器将执行擦除,这意味着它将用它们的边界替换泛型并添加强制转换以确保一切正常。BgetA

擦除后的类如下所示:

abstract class A {

   public abstract Object get();
}

class B extends A {

  @Override
  public String get() {
     return "hello";
  }
}

现在,由于 is 的方法签名get不是public Object get()类中存在的签名,B编译器将在类中生成一个桥接方法B以实现覆盖。

您可以认为类B如下所示。但是,重要的是要注意永远不会生成以下代码。它不会编译。编译器只是在bytecode中生成等效的get方法。

 class B extends A {

  //bridge method
  @Override
  public Object get() {
     return get();
  }

  public String get() {
     return "hello";
  }
}

B通过A该调用对类的每个多态使用都get将调用桥接方法,该桥接方法将委托给真正的 get 方法。

于 2013-09-06T10:49:46.113 回答
1

给定以下两个类:

public class Node<T> {
    private 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);
    }
}

在编译扩展参数化类或实现参数化接口的类或接口时,编译器可能需要创建一个合成方法,称为桥方法,作为类型擦除过程的一部分。

为了解决这个问题并在类型擦除后保留泛型类型的多态性,Java 编译器会生成一个桥接方法来确保子类型化按预期工作。对于 MyNode 类,编译器为 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);
    }

    // ...
}
于 2013-09-06T11:53:07.813 回答