Java Generics - Bridge Method

Java Generics - Bridge method?

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:

  • The Generics FAQ - What is a bridge method?
  • Java bridge methods explained (thanks @Bozho)

Bridge methods in Java generics. Is this example correct?

You almost got it correct. Almost, because bridge methods bridge methods calls and do not duplicate method implementations. Your IntItem class would look like the following desugared version (you can verify this using for example javap):

class IntItem extends Item<Integer> {

private Integer item;

// Bridge method 1
public void set(Object item) {
set((Integer) item);
}

public void set(Integer item) {
this.item = item;
}

//Bridge method 2
public Object get() {
return <Integer>get(); // pseudosyntax
}

public Integer get() {
return item;
}
}

Within Java byte code, it is allowed to define two methods that only differ by their return type. This is why there can be two methods get which you could not define explicitly using the Java language. As a matter of fact, you need to name the parameter types and the return type on any method invocation within the byte code format.

And this is why you need bridge methods in the first place. The Java compiler applies a type erasure on generic types. This means, generic types are not considered by the JVM which sees all occurences of Item<Integer> as a raw Item. However, this does not work well with the explicit naming of types. In the end, ItemInt is itself not longer generic as it overrides all methods with explicitly typed versions which would be visible to the JVM with these explicit types. Thus, IntItem would in its sugared version not even override any methods of Item because the signatures are not compatible. In order to make generic types transparent to the JVM, the Java compiler needs to insert these bridge methods which factually override the original implementations in order to bridge the invocations to the methods defined in IntItem. This way, you indirectly override the methods and get the behavior you expect. Consider the following scenario:

IntItem nonGeneric = new IntItem();
nonGeneric.set(42);

As mentioned, the IntItem::set(Integer) method is not generic because it is overridden with a non-generically typed method. Thus, there is no type erasure involved in calling this method. The Java compiler simply compiles the above method call to invoke the method with the byte code signature set(Integer): void. Just like you expected it.

However, when looking at the following code:

Item<Integer> generic = ...;
generic.set(42);

the compiler can not know for sure that the generic variable contains an instance of IntItem or of Item (or any other compatible class). Thus, there is no guarantee that a method with the byte code signature set(Integer): void even exists. This is why the Java compiler applies a type erasure and looks at Item<Integer> as if it was a raw type. By looking at the raw type, the method set(Object): void is invoked which is defined on Item itself and therefore always exists.

As a consequence, the IntItem class can not be sure if its methods are invoked using the method with the erased type (which it inherits from Item) or the methods with the explicit type. Bridge methods are therefore implemented implicitly to create a single version of truth. By dynamically dispatching the bridge, you can override set(Integer) in a subclass of IntItem and the bridge methods still work.

Bridge methods can also be used for implementing a covariant return type of a method. This feature was added when generics were introduced because bridge methods already existed as a concept. This feature came for free, so to speak. There is a third application of bridge methods where they implement a visibility bridge. This is necessary to overcome an access restriction of the reflection engine.

why aren't the bridge methods in Java preventing this and throwing an Exception?

Bridges (synthetic methods) aren't created unless you actually write the method, as you did in your followup test where you did put public boolean add(String o){return super.add(o);} in the code.

When you call .get(someInt) on an expression of type StringList, the compiler can see that your StringList type, even though it is a subtype of ArrayList<String>, doesn't have any method with signature String get(int) in it. Therefore, it generates a call to the Object get() method in the bytecode, and in addition (if needed) a cast, and at the java side of things it acts as if that get method on a StringList returns String. Even though at the bytecode level it does not.

If you then make a String get(int) method appear, for example by subclassing StringList, or by editing the source and recompiling just that file, then and only then is the bridge method generated. Because java is dynamic dispatch, that bridge is always used. The bridge casts and invokes (or invokes and casts, depends on whether the bridge is covering return type shenanigans or parameter shenanigans).

You can verify all this using javap -v: I strongly recommend all who find this interesting and want to know precisely how this works have some fun exploring the difference in outputs between this:

import java.util.*;

public class Test extends ArrayList<String> {
public static void main(String[] args) {
List raw = new Test();
raw.add(3);
}
}

and this:

import java.util.*;

public class Test extends ArrayList<String> {
public static void main(String[] args) {
List raw = new Test();
raw.add(3);
}
public boolean add(String o) {
return super.add(o);
}
}

Java synthetic method and bridge method confusion

If you are simply looking for an example of such:

Function<String, Integer> func = s -> s.length();

Arrays.stream(DeleteMe.class.getDeclaredMethods())
.peek(m -> System.out.println(m.getName() + " isSynth : " + m.isSynthetic() + " isBridge : " + m.isBridge()))
.forEach(System.out::println);

There will be entries like:

lambda$0 isSynth : true isBridge : false
lambda$1 isSynth : true isBridge : false

Writing Synthetic/Bridge method in java

Bridge methods in Java are synthetic methods, which are necessary to implement some of Java language features. The best known samples are covariant return type and a case in generics when erasure of base method's arguments differs from the actual method being invoked.

import java.lang.reflect.*;

/**
*
* @author Administrator
*/
class SampleTwo {

public static class A<T> {

public T getT(T args) {
return args;
}
}

static class B extends A<String> {

public String getT(String args) {
return args;
}
}
}

public class BridgeTEst {

public static void main(String[] args) {
test(SampleTwo.B.class);
}

public static boolean test(Class c) {
Method[] methods = c.getMethods();
for (Method method : methods) {

if (method.isSynthetic() || method.isBridge()) {
System.out.println("Method Name = "+method.getName());
System.out.println("Method isBridge = "+method.isBridge());
System.out.println("Method isSynthetic = "+method.isSynthetic());
return true;
}
// More code.
}
return false;
}
}

  • #Reference

See Also

  • what-java-lang-reflect-method-isbridge-used-for ?

How covarient return type is implemented using bridge method

I advise you to refer to Oracle material about it and to make some tests to understand the bridge mechanism.

Bridge is an artifact to go beyond the overriding method ability that was invariant at compile time before Java 1.5.

Java 1.5 supports covariant return types. What does this mean? Before
1.5, when you override a superclass method, the name, argument types and return type of the overrding method has to be exactly same as that
of superclass method. Overriding method is said to be invariant with
respect to argument types and return type.

If you change any argument type, then you are not really overriding a
method -- you are actually overloading it.

The bridge is a bridge : so it makes a link. Here it is between the method with the original return type and the overrided method with the covariant return type.

So yes you are right.

You want to check that ?
Compile the class and then disassemble the source code of it.

$ javap -c Point.class

You will get something like :


Compiled from "Point.java"
public class Point {
public Point(int, int);
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iload_1
6: putfield #2 // Field x:I
9: aload_0
10: iload_2
11: putfield #3 // Field y:I
14: return

protected Point clone() throws java.lang.CloneNotSupportedException;
Code:
0: new #4 // class Point
3: dup
4: aload_0
5: getfield #2 // Field x:I
8: aload_0
9: getfield #3 // Field y:I
12: invokespecial #5 // Method "":(II)V
15: areturn

protected java.lang.Object clone() throws java.lang.CloneNotSupportedException;
Code:
0: aload_0
1: invokevirtual #6 // Method clone:()LPoint;
4: areturn
}

You can see the delegation between Object clone() and Point clone().

Of course you cannot write such as code as return type based overloading is not allowed in Java at compile time but at runtime JVM can use this feature from compiled classes.

Does javac ever generate static bridge methods?

Bridge methods according to the Java Language Specification, which are the methods that should be annotated with ACC_BRIDGE, are there to ensure an override compatible signature, so that code invoking the method using the original signature will end up at the overridden method even if it has a different method signature at the bytecode level. The only applications in the Java programming language are type erasure and covariant return types.

Since static methods can not be overridden in that way that a caller could get redirected, there is no scenario in which a bridge method in the sense of the Java Language Specification can occur for a static method. Therefore, javac never produces a method that has ACC_BRIDGE and ACC_STATIC set.

It’s also a very questionable behavior to mark methods as bridge method on behalf of another language’s semantics. As the JVM specification says:

The ACC_BRIDGE flag is used to indicate a bridge method generated by a compiler for the Java programming language.

There are other synthetic delegate methods that might be static, like nested class accessors or adapters for method references (e.g. for varargs or intersection types). These do not count as bridge methods.



Related Topics



Leave a reply



Submit