How covariant method overriding is implemented using bridging Technique in java
Asked Answered
E

3

6

While reading on Covariant Overriding, i find out very strange fact,

covariant method overriding is implemented using a bridging technique. it also said that this feature is implemented in java5 and above.(i think it is because generics introduced from java5)

How it happens.Please help me with example.

Eurypterid answered 6/9, 2013 at 10:24 Comment(0)
D
8

Consider an example:

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

It looks good as of now. But, after type erasure, the interface loose it's generic type, and the type T is replaced with the upper bound. So the interface and class looks like:

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

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

Now, here's the problem. The method in Circle after erasure is not really an overridden version of Shape. Note that, now the methods as it looks, applies greater restriction on the parameter it takes, and the value it returns. This is because the erasure changes the signature of method in interface.

To solve this issue, the compiler adds bridge method for those, which delegates the call to those actual methods in class.

So, the class is really converted to:

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
}

So, the bridge method is now the overridden version of the methods in the interface, and they delegate the call to the actual method that does the task.

Note that the type used in the bridge method is the erasure of the type parameter of the interface, in this case Shape.


References:

Dunedin answered 6/9, 2013 at 10:37 Comment(2)
Very interesting! And there's no error because of two methods that have the same name/parameters within a same class? I.e. the "Duplicate method getType() in type Circle" message?Closet
@sp00m: there would be an error if you tried to pass that to a Java compiler, because methods can't differ only by return type. But at the JVM bytecode level, that restriction doesn't exist.Broadcasting
J
1

What this means is that if you have a method with a covariant (narrower) return type, the compiler will create a synthetic bridge method for you and invoke the overriding method through this bridge method. Covariance in return type was introduced in Java 5, at the same time as generics and it is implemented using bridge methods.

Example 1 : Covariance in return type is also implemented using synthetic bridge methods even when there are no generics present.

For example:

abstract class A {     

   public abstract A get();
}

class B extends A {

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

Will be implemented in bytecode by the compiler using a synthetic bridge method that delegates to the original method. In principle you can imagine that the compiler translates the covariant override to 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;
  }
}

Example 2 - Generics : Let's look at an example when generics are involved.

abstract class A<T> {     

   public abstract T get();
}

class B extends A<String> {

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

The method get() of class B is covariant in return type to the method get in class A. When compiling this bit of code the compiler will perform erasure which means that it will replace generics with their bounds and add casts to make sure that the everything works.

After erasure the classes look like this:

abstract class A {

   public abstract Object get();
}

class B extends A {

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

Now since the method signature of get is public Object get() which is not a signature that is present in class B the compiler will generate a bridge method in class B in order to achieve the overriding.

You can think of class B looking like the below. However, it is important to note that the below code will never be generated. It would not compile. The compiler just generates the equivalent get method in bytecode.

 class B extends A {

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

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

Every polymorphic use of class B through A that invokes get will invoke the bridge method which will delegate to the real get method.

Jodi answered 6/9, 2013 at 10:49 Comment(0)
C
1

Given the following two classes:

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

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, called a bridge method, as part of the type erasure process.

To solve this problem and preserve the polymorphism of generic types after type erasure, a Java compiler generates a bridge method to ensure that subtyping works as expected. For the MyNode class, the compiler generates the following bridge method for 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);
    }

    // ...
}
Cockatiel answered 6/9, 2013 at 11:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.