Java Generics - Bridge method?
Asked Answered
P

4

77

Something called the "bridge method" concept related to Java Generics made me stop at a point and think over it.

Btw, I only know that it occurs at the bytecode level and is not available for us to use.

But I am eager to know the concept behind the "bridge method" used by the Java compiler.

What exactly happens behind the scenes and why it is used?

Any help with an example would be greatly appreciated.

Piercy answered 15/2, 2011 at 17:48 Comment(1)
I can't explain it clearer than this: stas-blogspot.blogspot.com/2010/03/… (which happens to be the 1st google result)Siddra
P
103

It's a method that allows a class extending a generic class or implementing a generic interface (with a concrete type parameter) to still be used as a raw type.

Imagine this:

public class MyComparator implements Comparator<Integer> {
   public int compare(Integer a, Integer b) {
      //
   }
}

This can't be used in its raw form, passing two Objects to compare, because the types are compiled in to the compare method (contrary to what would happen were it a generic type parameter T, where the type would be erased). So instead, behind the scenes, the compiler adds a "bridge method", which looks something like this (were it Java source):

public class MyComparator implements Comparator<Integer> {
   public int compare(Integer a, Integer b) {
      //
   }

   //THIS is a "bridge method"
   public int compare(Object a, Object b) {
      return compare((Integer)a, (Integer)b);
   }
}

The compiler protects access to the bridge method, enforcing that explicit calls directly to it result in a compile time error. Now the class can be used in its raw form as well:

Object a = 5;
Object b = 6;

Comparator rawComp = new MyComparator();
int comp = rawComp.compare(a, b);

Why else is it needed?

In addition to adding support for explicit use of raw types (which is mainly for backwards compatability) bridge methods are also required to support type erasure. With type erasure, a method like this:

public <T> T max(List<T> list, Comparator<T> comp) {
   T biggestSoFar = list.get(0);
   for ( T t : list ) {
       if (comp.compare(t, biggestSoFar) > 0) {
          biggestSoFar = t;
       }
   }
   return biggestSoFar;
}

is actually compiled into bytecode compatible with this:

public Object max(List list, Comparator comp) {
   Object biggestSoFar = list.get(0);
   for ( Object  t : list ) {
       if (comp.compare(t, biggestSoFar) > 0) {  //IMPORTANT
          biggestSoFar = t;
       }
   }
   return biggestSoFar;
}

If the bridge method didn't exist and you passed a List<Integer> and a MyComparator to this function, the call at the line tagged IMPORTANT would fail since MyComparator would have no method called compare that takes two Objects...only one that takes two Integers.

The FAQ below is a good read.

See Also:

Parthenon answered 15/2, 2011 at 17:51 Comment(11)
Also for covariant return types even in the absence of generics.Mickimickie
@paulmurray: what is "equals(T o)"?Botvinnik
@Tom Hawtin - tackline can you clarify about covariant return type?Ointment
@Tom Hawtin is it only for comply old(before java 5) overriding rules?Ointment
@Ointment Suppose you have a method Object get() and then override it in a subclass with, say, String get(). The JVM will only override exactly matching signatures including the return type. So javac creates a synthetic bridge method Object get() (a.k.a. get()Ljava/lang/Object;) with an implementation that calls String get() (a.k.a. get()Ljava/lang/String;). This has only been possible in source code since 1.5, and you can't target an earlier version of bytecode than the source code.Mickimickie
@Mark, is a bridge method in your example public?Gelding
@AndrewTobilko: Yes, it is public as reported by javap -v MyComparator.class. However, as I mentioned, the compiler prevents you from calling the bridge method directly on a parameterized type, so I'd say it's "public with a caveat". If you look at the output of javap, you will see that the bridge method, despite having ACC_PUBLIC, has two additional flags: ACC_BRIDGE and ACC_SYNTHETIC. This is what the compiler will look at use to prohibit access.Parthenon
@AndrewTobilko: Note also that doesn't mean all bridge methods are public, just the one in my example. If you were extending a class (and giving it a concrete type) with a protected generic method, and overrode it with a protected method, the bridge method would be protected to match.Parthenon
@MarkPeters , what do you mean by: "This can't be used in its raw form, passing two Objects to compare, because the types are compiled in to the compare method", isn't Integer a concrete class already? how is it raw?Forgiveness
@juztcode: Meaning if you passed the Comparator to an existing raw function, say max(Iterable items, Comparator cmp) without the bridge method, calls within max to comparator.compare(a, b) would fail because a and b would have static type Object, and your comparator would have no method with the signature int compare(Object, Object).Parthenon
Considering "The compiler protects access to the bridge method, enforcing that explicit calls directly to it result in a compile time error. " Interesting, how compiler protects access to the public bridge method? It seems calling it with wrong type would produce run-time exception, instead of compile-time error. docs.oracle.com/javase/tutorial/java/generics/…Pirouette
F
14

If you want to understand why you need bridge method, you better understand what happens without it. Suppose there is no bridge method.

class A<T>{
  private T value;
  public void set(T newVal){
    value=newVal
  }
}

class B extends A<String>{
  public void set(String newVal){
    System.out.println(newVal);
    super.set(newVal);
  }
}

Notice that after erasure, method set in A became public void set(Object newVal) since there is no bound on Type parameter T. There is no method in class B the signature of which is the same as set in A. So there is no override. Hence, when something like this happened:

A a=new B();
a.set("Hello World!");

Polymorphism won't work here. Remember you need to override the method of parent class in child class so that you can use parent class var to trigger polymorphism.

What bridge method does is silently override the method in parent class with all the information from a method with the same name but a different signature. With the help of the bridge method, polymorphism worked. Though on the surface, you override the parent class method with a method of different signature.

Formant answered 30/1, 2018 at 9:59 Comment(2)
I checked the whole internet but found your answer very easy and to the point. thanks million times.Dungaree
@SunnyKhan That was me when I saw bridge method for the first time! Luckily I figured out how it works!Formant
K
0

It's insteresting to note that the compiler infers that MyComparator's method:

public int compare(Integer a, Integer b) {/* code */}

is trying to override Comparator<T>'s

public int compare(T a, T b);

from the declared type Comparator<Integer>. Otherwise, MyComparator's compare would be treated by the compiler as an additional (overloading), and not overridding, method. And as such, would have no bridge method created for it.

Kip answered 2/11, 2014 at 21:37 Comment(0)
K
0

As indicated by this article and this article, the key reason of the Java bridge method is Type Erasure and Polymorphism.

Let's take the class ArrayDeque (source code) as example, it contains a clone() method as bellow, because the class ArrayDeque implements the Cloneable interface so it must override the Object.clone() method.

public class ArrayDeque<E> extends AbstractCollection<E>
                        implements Deque<E>, Cloneable, Serializable
{

  public ArrayDeque<E> clone() {
    ....
  }
}

UML Hierarchy Diagram of ArrayDeque

But the problem is the return type of ArrayDeque.clone() is ArrayDeque<E>, and it did not match to the method signature defined in the parent Object.clone(), and in Object.java the return type is Object instead.

public class Object {

    protected native Object clone() throws CloneNotSupportedException;
}

The return type mismatch is a problem for Polymorphism. So in the compiled result file ArrayDeque.class, the Java compiler generated two clone() methods, one match the signature in the source code, the other one match to the signature in the parent class Object.clone().

  1. clone() method returns ArrayDeque<E>, which is generated based on the corresponding source code
  2. clone() method returns Object, which is generated based on Object.clone(). This method is doing nothing but calling the other clone() method. And, this method is tagged as ACC_BRIDGE, which indicates this method is generated by the compiler for the Bridge purpose.

UML diagram generated based on ArrayDeque.class

Koal answered 23/10, 2019 at 4:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.