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.
It's A🐶 Dog doesn't know anything about Shiba or any other classes, so we can just look at the Dog.
What about when we call:
Shiba doge =newShiba();rarePupper.eat();
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:
Dog confuzzled =newShiba();
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:
/** The following problems are inspired by Spring 2020 Exam Prep 5. */publicclassDog {publicvoidplayWith(Dog d) { ... } // D}publicclassShibaextendsDog { @OverridepublicvoidplayWith(Dog d) { ... } // EpublicvoidplayWith(Shiba s) { ... } // F}
Which method(s) run when we call:
Dog rarePupper =newShiba();rarePupper.playWith(rarePupper); // aww rarePupper is lonely :(
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'sdynamic type is Shiba but its static type is Dog so Shiba.playWith(Dog) is chosen as the method.
Which is called when we run:
Dog rarePupper =newShiba();Shiba doge =newShiba();rarePupper.playWith(doge); // rarePupper is happy :) borks all around
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.
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.
Dog d =newShiba();System.out.println(d.getType());
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.)
What prints out when we run:
Shiba s =newShiba();System.out.println(s);
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.
What prints out when we run:
Dog d =newShiba();System.out.println(((Shiba)d).getType());
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.