This lecture is in the context of the Java programming language.
Overloading: methods sharing a name.
public class OverloadingSimple {
public String m(int a) {
return "my parameter is an int";
}
public String m(String b) {
return "my parameter is a string";
}
public static void main(String[] args) {
System.out.println(new OverloadingSimple().m(123));
System.out.println(new OverloadingSimple().m("a"));
}
}
Seems nice at first to avoid thinking about names.
class A {
public void f(A a) { print("A.f(A)"); }
}
class B extends A {
public void f(A a) { print("B.f(A)"); }
public void f(B b) { print("B.f(B)"); }
}
A a = new B(); // take care, this is a B!
B b = new B();
a.f(a); a.f(b);
b.f(a); b.f(b);
What are the results of these 4 expressions?
class A {
public void f(A a) { print("A.f(A)"); }
}
class B extends A {
public void f(A a) { print("B.f(A)"); }
public void f(B b) { print("B.f(B)"); }
}
A a = new B(); // take care, this is a B!
B b = new B();
a.f(a); a.f(b);
b.f(a); b.f(b);
B.f(A) | B.f(A) |
B.f(A) | B.f(B) |
Overloading + sub-typing is confusing in Java.
In the presence of a message send, the compiler searches a compatible signature and stores it. During execution, the lookup algorithm searches for a method with this exact signature, regardless of the dynamic types of arguments.
public class A {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Visitor {
public void visit(A a) {
System.out.println("Visitor.visit(A)");
}
}
Visitor visitor = new Visitor();
new A().accept(visitor); // "Visitor.visit(A)"
All is well in this perfect world...
public class SubA extends A {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class SubVisitor extends Visitor {
public void visit(SubA subA) {
System.out.println("SubVisitor.visit(SubA)");
}
}
Visitor visitor = new SubVisitor();
new SubA().accept(visitor); // "Visitor.visit(A)"
When method SubA.accept()
is compiled, the message visitor.visit(this)
is bound to visit(A)
, the only compatible signature in Visitor
. During execution, visit(SubA)
is ignored.
One way to fix that could be to add a visit(SubA)
method in Visitor
(if you can change it):
public class Visitor {
public void visit(A a) {
System.out.println("Visitor.visit(A)");
}
public void visit(SubA a) {
System.out.println("Visitor.visit(SubA)");
}
}
This works but has 2 problems (see next slides):
accept()
in A
and SubA
are identicalvisit()
methods directly
The methods accept()
in A
and SubA
are identical:
public class A {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class SubA extends A {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
You could think that SubA.accept()
is useless because its content is the same as A.accept()
. But it's not useless as, inside it, the message visitor.visit(this)
binds to the signature visit(SubA)
which is what we want.
You can't use the visit()
methods directly:
public class Visitor {
public void visit(A a) {
System.out.println("Visitor.visit(A)");
}
public void visit(SubA a) {
System.out.println("Visitor.visit(SubA)");
}
}
Visitor visitor = new Visitor();
A subA = new SubA();
visitor.visit(subA); // "Visitor.visit(A)"
If you use the visitor directly (i.e., without going through accept()
methods), the static types become important to decide which visit()
method to execute.
Don't use overloading and prefer dedicated method names:
public class A {
public void accept(Visitor visitor) {
visitor.visitA(this);
}
}
public class Visitor {
public void visitA(A a) {
System.out.println("Visitor.visitA(A)");
}
}
/