Java's Dynamic Method Selection
This article is fairly under-construction...
Java's dynamic method selection is painful to understand if you don't get its design methodology.
We're gonna go through a few simple code snippets to make sure we can understand it with keeping some really simple principles in mind.
Suppose we have the following interface and class implementation:
public interface Animal {
default void greet(Animal a) {
System.out.println("greet :: Animal!");
}
default void sniff(Animal a) {
System.out.println("sniff :: Animal!");
}
default void flatter(Animal a) {
System.out.println("flatter :: Animal!");
}
}
public class Dog implements Animal {
public void sniff(Animal a) {
System.out.println("sniff :: Dog!");
}
public void flatter(Dog d) {
System.out.println("flatter :: Dog!");
}
}
Let's assume we have the following lines of code in a valid main function that can be run directly. What is the expected output for the program?
Animal a = new Dog();
Dog d = new Dog();
a.greet(d);
It should be not hard for you to convince yourself a line with greet :: Animal!
will be printed out. OK, what about the following line?
d.flatter(d);
d.flatter(a);
The answer is
flatter :: Dog!
flatter :: Animal!
You should be able to notice that the flatter
method in class Dog
is actually overloading the flatter
method in interface Animal
rather than overriding it (the method signatures are different!). But how does Java handle all these complicated things?
Be relaxed, let's understand and remember the following principles:
- If we have a variable has static type
X
and dynamic typeY
, then ifY
overrides the method,Y
's method is used instead.- At compile time, the compiler will verify
X
has a method that can handle the given parameter. If multiple methods can handle, it records the most specific one.- At runtime, if
Y
overrides the recorded signature, use the overridden method.
We provide a more concrete example and comments for a better understanding of these principles.
public class Main {
public static void main(String[] args) {
// static: Animal
// dynamic: Dog
Animal a = new Dog();
// static: Dog
// dynamic: Dog
Dog d = new Dog();
// compile: Animal's `greet(Animal a)` is recorded
// run: not found Dog's `greet(Animal a)`
// result: Animal.greet
a.greet(d);
// compile: Animal's `sniff(Animal a)` is recorded
// run: found Dog's `sniff(Animal a)`
// result: Dog.sniff
a.sniff(d);
// compile: Dog's `sniff(Animal a)` is recorded
// run: keep Dog's `sniff(Animal a)`
// result: Dog.sniff
d.sniff(a);
// compile: Animal's flatter(Animal a) is recorded
// run: not found Dog's flatter(Animal a)
// result: Animal.flatter
a.flatter(d);
// compile: Animal's flatter(Animal a) is recorded
// run: not found Dog's flatter(Animal a)
// result: Animal.flatter
a.flatter(a);
// compile: Dog's flatter(Dog d) is recorded
// run: keep Dog's flatter(Dog d)
// result: Dog.flatter
d.flatter(d);
// compile: Animal's flatter(Animal a) is recorded,
// as Dog does have a flatter method but not with the same signature.
// run: not found Dog's flatter(Animal a)
// result: Animal.flatter
d.flatter(a);
}
}
It worth to note that although in the example above Animal
is an interface, the exact same idea can be applied for extends
as well. Another example is provided here for a practice purpose.
public class Main {
public static void main(String[] args) {
// static: Bird
// dynamic: Falcon
Bird bird = new Falcon();
// static: Falcon
// dynamic: Falcon
Falcon falcon = (Falcon) bird;
// compile: Bird's `gulgate(Bird b)` is recorded
// run: not found Falcon's `gulgate(Bird b)`
// result: Bird.gulgate
bird.gulgate(bird);
// compile: Bird's `gulgate(Bird b)` is recorded
// run: not found Falcon's `gulgate(Bird b)`
// result: Bird.gulgate
bird.gulgate(falcon);
// compile: Bird's `gulgate(Bird b)` is recorded
// as Falcon does have a gulgate method but not with the same signature.
// run: not found Falcon's `gulgate(Bird b)`
// result: Bird.gulgate
falcon.gulgate(bird);
// compile: Falcon's `gulgate(Falcon f)` is recorded
// run: keep Falcon's `gulgate(Falcon f)`
// result: Falcon.gulgate
falcon.gulgate(falcon);
}
}
class Bird {
public void gulgate(Bird b) {
System.out.println("Bird Gul!");
}
}
class Falcon extends Bird {
public void gulgate(Falcon f) {
System.out.println("Falcon Gul!");
}
}