Wednesday, September 15, 2010

Run time and compile time method resolution in Java

This post is thanks to a colleague at work.

Suppose you have a class Dog and a class Mammal it inherits from. Both have a method sayHello().

public static class Mammal {
public void sayHello() {
System.out.println("Hi, I'm a mammal!");
}
}

public static class Dog extends Mammal {
@Override
public void sayHello() {
System.out.println("Hi, I'm a mammal - in fact I'm a dog!");
}
}

If you were to call sayHello() on an instance of Dog which is declared (to the compiler) as a Mammal:

private Dog getADog() {
return new Dog();
}

public void runExample() {

Mammal aMammal = getADog();

System.out.println("Run time method resolution");
aMammal.sayHello();
.......

This would print:

Run time method resolution
Hi, I'm a mammal - in fact I'm a dog!

No surprises there, just a virtual method.

But now, suppose you have:

public void makeSayHello(Mammal aMammal) {
System.out.println("Compile time method resolution for a MAMMAL");
System.out.print("Corresponding run time method resolution: ");
aMammal.sayHello();
}

public void makeSayHello(Dog aDog) {
System.out.println("Compile time method resolution for a DOG");
System.out.print("Corresponding run time method resolution: ");
aDog.sayHello();
}

and in the example you call:

public void runExample() {

Mammal aMammal = getADog();

System.out.println("Run time method resolution");
aMammal.sayHello();

System.out.println();

System.out.println("Compile time method resolutions");
makeSayHello(aMammal);

Dog aDog = (Dog) aMammal;
makeSayHello(aDog);
}

aMammal is always the same object - a dog. In the second occurence, it's explicity cast as such.

Will both calls to makeSayHello use the makeSayHello(Dog aDog) method?

No - the first call will use makeSayHello(Mammal aMammal). Why? Because when it comes to method parameters, the resolution of overloaded methods is done at compile time. Otherwise stated, when overriding methods the 'closest' version will we used, by virtue of virtuality (pardon the repetition). When overloading methods, the version called will be the one that matches the compile time declaration of the parameter.

Note that the calls to aMammal.sayHello() and aDog.sayHello() in the respective makeSayHello() methods will call the Dog implementation (here we are using method overriding again).

The complete example follows:


public class OverloadingExamples {

public static class Mammal {
public void sayHello() {
System.out.println("Hi, I'm a mammal!");
}
}

public static class Dog extends Mammal {
@Override
public void sayHello() {
System.out.println("Hi, I'm a mammal - in fact I'm a dog!");
}
}

private Dog getADog() {
return new Dog();
}

public void runExample() {

Mammal aMammal = getADog();

System.out.println("Run time method resolution");
aMammal.sayHello();

System.out.println();

System.out.println("Compile time method resolutions");
makeSayHello(aMammal);

Dog aDog = (Dog) aMammal;
makeSayHello(aDog);
}

public void makeSayHello(Mammal aMammal) {
System.out.println("Compile time method resolution for a MAMMAL");
System.out.print("Corresponding run time method resolution: ");
aMammal.sayHello();
}

public void makeSayHello(Dog aDog) {
System.out.println("Compile time method resolution for a DOG");
System.out.print("Corresponding run time method resolution: ");
aDog.sayHello();
}

public static void main(String args[]) {
new OverloadingExamples().runExample();
}
}

And here's the output:

Run time method resolution
Hi, I'm a mammal - in fact I'm a dog!

Compile time method resolutions
Compile time method resolution for a MAMMAL
Corresponding run time method resolution: Hi, I'm a mammal - in fact I'm a dog!
Compile time method resolution for a DOG
Corresponding run time method resolution: Hi, I'm a mammal - in fact I'm a dog!

No comments: