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.

public class Dog {
    public void eat() { ... } // A
}

public class Shiba extends Dog {
    @Override
    public void eat() { ... } // C
}

Which method is called when we run:

Dog rarePupper = new Dog();
rarePupper.eat();

What about when we call:

Shiba doge = new Shiba();
rarePupper.eat();

Things Get Wonky: Mismatched Types

There's an interesting case that actually works in Java:

Dog confuzzled = new Shiba();

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. */

public class Dog {
    public void playWith(Dog d) { ... } // D
}

public class Shiba extends Dog {
    @Override
    public void playWith(Dog d) { ... } // E
    public void playWith(Shiba s) { ... } // F
}

Which method(s) run when we call:

Dog rarePupper = new Shiba();
rarePupper.playWith(rarePupper); // aww rarePupper is lonely :(

Which is called when we run:

Dog rarePupper = new Shiba();
Shiba doge = new Shiba();
rarePupper.playWith(doge); // rarePupper is happy :) borks all around

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!

public class Dog {
    public static String getType() {
        return "cute doggo";
 
    @Override // Remember, all objects extend Object class!   
    public String toString() {
        return getType();
    }
}

public class Shiba extends Dog {
    public static String getType() {
        return "shiba inu";
    }
}

What prints out when we run:

Dog d = new Shiba();
System.out.println(d.getType());

What prints out when we run:

Shiba s = new Shiba();
System.out.println(s);

What prints out when we run:

Dog d = new Shiba();
System.out.println(((Shiba)d).getType());

That's all, folks!

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

Last updated