CS61B Guide
Searchโ€ฆ
Dynamic Method Selection
ft. Doge and RarePupper
This is a very tricky topic. Make sure you are comfortable with inheritance and access control before proceeding!
Inheritance is great and all, but it does have some issues. One of the biggest issues lies in overriding: if two methods have exactly the same name and signature, which one do we call?
In a standard use case, this is a pretty simple answer: whichever one is in the class we want! Let's look at some basic examples.
1
public class Dog {
2
public void eat() { ... } // A
3
}
4
โ€‹
5
public class Shiba extends Dog {
6
@Override
7
public void eat() { ... } // C
8
}
Copied!
Question 1
Q1 Answer
Which method is called when we run:
1
Dog rarePupper = new Dog();
2
rarePupper.eat();
Copied!
It's A
๐Ÿถ
Dog doesn't know anything about Shiba or any other classes, so we can just look at the Dog.
Question 2
Q2 Answer
What about when we call:
1
Shiba doge = new Shiba();
2
rarePupper.eat();
Copied!
This calls C! This works intuitively because Shiba overrides Dog so all Shibas will use C instead of A.

Things Get Wonky: Mismatched Types

There's an interesting case that actually works in Java:
1
Dog confuzzled = new Shiba();
Copied!
What??? Shouldn't this error because Dog is incompatible with Shiba?
It turns out that subclasses can be assigned to superclasses. In other words, Parent p = new Child() works fine. This is really useful for things like Interfaces and generic Collections because we might only care about using generic methods, and not the specific implementation that users chose to provide.
However, it is important to note that it doesn't work the other way. Child c = new Parent() will error because the child might have new methods that don't exist in the parent.
Let's see how this makes inheritance really tricky:
1
/** The following problems are inspired by Spring 2020 Exam Prep 5. */
2
โ€‹
3
public class Dog {
4
public void playWith(Dog d) { ... } // D
5
}
6
โ€‹
7
public class Shiba extends Dog {
8
@Override
9
public void playWith(Dog d) { ... } // E
10
public void playWith(Shiba s) { ... } // F
11
}
Copied!
Question 3
Q3 Answer
Which method(s) run when we call:
1
Dog rarePupper = new Shiba();
2
rarePupper.playWith(rarePupper); // aww rarePupper is lonely :(
Copied!
E is called! What happens is that the dynamic type is chosen to select the method from, but the static type is used to select the parameters. rarePupper's dynamic type is Shiba but its static type is Dog so Shiba.playWith(Dog) is chosen as the method.
rarePupper in action
Question 4
Q4 Answer
Which is called when we run:
1
Dog rarePupper = new Shiba();
2
Shiba doge = new Shiba();
3
rarePupper.playWith(doge); // rarePupper is happy :) borks all around
Copied!
E is called again! Bet ya didn't see that coming ๐Ÿ˜Ž
Why is it not F? I thought doge and rarePupper were both Shiba? When the compiler chooses a method, it always starts at the static method. Then, it keeps going down the inheritance tree until it hits the dynamic method. Since F has a different signature than D, it isn't an overriding method and thus the compiler won't see it. But E is (since it has the same signature as D), so that is why it is chosen instead.
bork bork bork :DDD

Adding more insanity: Static vs. Dynamic

By now, you should have a pretty good understanding of the method selection part of DMS. But why is it dynamic?
You may have noticed that there are two type specifiers in an instantiation. For example, Dog s = new Shiba() has type Dog on the left and Shiba on the right.
Here, Dog is the static type of s: it's what the compiler believes the type should be when the program is compiled. Since the program hasn't run yet, Java doesn't know what exactly it is- it just knows it has to be some type of Dog.
Conversely, Shiba is the dynamic type: it gets assigned during runtime.

The type rules

Just remember: like chooses like. If a method is static, then choose the method from the static type. Likewise, if a method is not static, choose the corresponding method from the dynamic type.
Let's try some examples!
1
public class Dog {
2
public static String getType() {
3
return "cute doggo";
4
5
@Override // Remember, all objects extend Object class!
6
public String toString() {
7
return getType();
8
}
9
}
10
โ€‹
11
public class Shiba extends Dog {
12
public static String getType() {
13
return "shiba inu";
14
}
15
}
Copied!
Question 5
Q5 Answer
What prints out when we run:
1
Dog d = new Shiba();
2
System.out.println(d.getType());
Copied!
cute doggo gets printed because getType() is a static method! Therefore, Java looks at the static type of d, which is Dog. (If getType() weren't static, then shiba inu would have been printed as usual.)
Question 6
Q6 Answer
What prints out when we run:
1
Shiba s = new Shiba();
2
System.out.println(s);
Copied!
cute doggo also gets printed!! This is because static methods cannot be overridden. When toString() is called in Dog, it doesn't choose Shiba's getType() because getType() is static and the static type is Dog.
Question 7
Q7 Answer
What prints out when we run:
1
Dog d = new Shiba();
2
System.out.println(((Shiba)d).getType());
Copied!
This time, shiba inu gets printed. This is because casting temporarily changes the static type: since the static type of d is Shiba in line 2, it chooses the getType() from Shiba.

That's all, folks!

If you want some even harder problems, check this out and also this.
bai bai!