Overloading
Java classes can contain multiple methods with the same name, if they have different signatures, e.g.
class Circle {
int r;
// ...
@Override
public boolean equals(Object x) {
return false;
}
public boolean equals(Circle c) {
return c.r == this.r;
}
}Dynamic Binding
How do we know which method to call when we execute the program?
class ColouredCircle extends Circle {
int colour;
@Override
public boolean equals(Object x) {
return false;
}
public boolean equals(ColouredCircle c) {
return super.equals(c) && this.colour = c.colour;
}
}
ColouredCircle cc = new ColouredCircle(1,1);
Circle c = new ColouredCircle(1,2);
System.out.println(c.equals(cc)); // <- How does this line work
// prints true1. At compile time
- Identify the compile-time type of
c(Circle), and CTT ofcc(ColouredCircle) - List down all the accessible
equalsmethods fromCircle - Choose the most specific method descriptor that does not lead to a compilation error in this case
Circle::equals(Circle)- since it is more specific than
Circle::equals(Object) - does not lead to a compilation error when a
ColouredCircle(compile-time type ofcc) is passed to it - note:
<T> void fun(T)is always more general
- since it is more specific than
- (Perform type erasure if necessary)
- Store this method descriptor in the compiled bytecode (store ERASED descriptor)
2. At run time
- Determine run-time type of
c(ColouredCircle) - Look for an accessible method with a matching descriptor
- First, look in the current class (
ColouredCircle) - If not found, look up the parents until found (this will always find something, because the compiler got the method descriptor from a parent in the first place)
- The first matching method will be executed (most specific descriptor after type erasure)
- First, look in the current class (
Most Specific Method Signature?
A method is more specific than a method if the arguments to can be passed to without compilation error. So hi(ColouredCircle) is more specific than hi(Circle)
Bridge Method
// Before
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
super.setData(data);
} // This intends to override setData
}
MyNode mn = new MyNode(1);
mn.setData("hello");
// After type erasure & bridge method
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Object /* ERASED parent type */ data) {
super.setData((Integer /* original Type param */) data); // this refers to MyNode::setData
} // BRIDGE METHOD
public void setData(Integer data) {
super.setData(data);
}
}- A bridge method is generated when both:
- A type extends/implements a parameterised type
- Type erasure changes the signature of one or more inherited methods
- The bridge method is generated right after type erasure
- Note that in the example above
- We expect the signatures before type erasure to match, since
Node::setData(T)andTis integer, andMyNode::setData(Integer) - But the signatures after type erasure do not match:
Node::setData(Object)vsMyNode::setData(Integer) - So, calling
myNode.setData("hello")will work, becauseMyNode::setDatais just overloadingNode::setData. - The bridge method ensures the overriding works as expected
- We expect the signatures before type erasure to match, since