Как реализовано переопределение ковариантного метода с использованием метода моста в Java

Читая Covariant Overriding, я обнаружил очень странный факт,

Переопределение ковариантного метода реализовано с использованием метода моста. он также сказал, что эта функция реализована в java5 и выше (я думаю, это потому, что дженерики введены из java5)

Как это происходит. Пожалуйста, помогите мне с примером.

3 ответа

Решение

Рассмотрим пример:

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) {  }
}

Теперь вот проблема. Метод в Circle после стирания не является переопределенной версией 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,


Рекомендации:

Это означает, что если у вас есть метод с ковариантным (более узким) типом возврата, компилятор создаст для вас метод искусственного моста и вызовет переопределяющий метод через этот метод моста. Ковариантность в возвращаемом типе была введена в 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";
  }
}

Метод get() класса B ковариантен в типе возврата в метод get в классе A, При компиляции этого фрагмента кода компилятор выполнит стирание, что означает, что он заменит обобщенные значения их границами и добавит приведение, чтобы убедиться, что все работает.

После удаления классы выглядят так:

abstract class A {

   public abstract Object get();
}

class B extends A {

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

Теперь, так как метод подписи get является public Object get() которая не является подписью, которая присутствует в классе B компилятор сгенерирует метод моста в классе B для того, чтобы добиться переопределения.

Вы можете думать о классе B выглядит как ниже. Однако важно отметить, что приведенный ниже код никогда не будет сгенерирован. Это не будет компилироваться. Компилятор просто генерирует эквивалент get метод в байт-коде.

 class B extends A {

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

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

Каждое полиморфное использование класса B через A что вызывает get вызовет метод моста, который делегирует реальный метод get.

Даны следующие два класса:

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);
    }

    // ...
}
Другие вопросы по тегам