Java Generics - Bridge method?
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.
Wha开发者_开发知识库t exactly happens behind the scenes and why it is used?
Any help with an example would be greatly appreciated.
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 Object
s 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 Object
s...only one that takes two Integer
s.
The FAQ below is a good read.
See Also:
- The Generics FAQ - What is a bridge method?
- Java bridge methods explained (thanks @Bozho)
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.
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.
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() {
....
}
}
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()
.
- clone() method returns
ArrayDeque<E>
, which is generated based on the corresponding source code - clone() method returns
Object
, which is generated based onObject.clone()
. This method is doing nothing but calling the otherclone()
method. And, this method is tagged as ACC_BRIDGE, which indicates this method is generated by the compiler for the Bridge purpose.
精彩评论