@Sandeep - regarding your latest comment (at the time of this writing)...
If in Java, all non-static methods are virtual by default,
why the does the books says "To reiterate, the compiler
looks only at the reference type, not the instance type"?
Isn't this statement amounting to compile time binding?
I think the book is a bit incomplete...
By 'reference type' the book is talking about how a given variable is declared; we can call that the variable's class. One thing that will help you coming from C++ is to think of all Java as variables as pointers to a particular instance (except primitive types like 'int'). It is easy enough to say everything in Java is "pass by value", but because variables are always pointers it is the pointer value that gets pushed onto the stack whenever a method call is made... the object instances themselves stay in the same place on the heap.
This is what I was originally writing before I noticed the comment...
The ideas of "Compile time" and "run time" are not that helpful (for me) for for predicting behavior.
I say that because a more useful question (for me) is "How do I know what method will be called at runtime?"
And by "How do I know" I mean "How do I predict" ?
Java instance methods are driven by whatever the instance actually is (virtual functions in C++).
An instance of Class Horse instance will always be a Horse instance.
The following are three different variables ("reference types" to use the books phrasing) that all happen to refer to the same instance of Horse.
Horse x = new Horse();
Animal y = x;
Object z = x;
Java class methods (basically any method w/'static' in front of it) are less intuitive and are pretty much limited to the exact class they refer to in the source code, which means "bound at compile time."
Consider the test output (below) when reading the following:
I added another variable to your TestAnimals class, and played with the formatting a little...
In main() we now have 3 variables:
Animal a = new Animal();
Animal b = new Horse();
Horse c = new Horse(); // 'c' is a new variable.
I tweaked the output of eat() a little bit.
I also added a class method xyz() to both Animal & Horse.
From the printouts you can see they are all different instances.
On my computer, 'a' points to Animal@42847574 (yours will say Animal@some_number, the actual number will vary from one run to the next).
'a' points to Animal@42847574
'b' points to Horse@63b34ca.
'c' points to Horse@1906bcf8.
So at the beginning of main() we have one 'Animal' instance and two different 'Horse' instances.
The big difference to observe is how .eat() behaves and how .xyz() behaves.
Instance methods like .eat() pay attention to the instance's Class.
It doesn't matter what Class the variable is that is pointing to the instance.
Class methods, on the other hand, always follow however the variable is declared.
In the example below, even though Animal 'b' refers to a Horse instance, b.xyz() invokes Animal.xyz(), not Horse.xyz().
Contrast this with Horse 'c' which does cause c.xyz() to invoke the Horse.xyz() method.
This drove me CRAZY when I was learning Java; in my humble opinion it was a cheap way to save a method lookup at runtime. (And to be fair, in the mid 1990's when Java was being created, maybe it was important to take performance shortcuts like that).
Anyway, may be more clear after I reassign Animal 'a' to the same Horse that 'c':
a = c;
Now a and c point to same instance:
Animal a=Horse@1906bcf8
Horse c=Horse@1906bcf8
Consider the behavior of both Animal 'a' and Horse 'c' at after that.
Instance methods still do whatever the instance actually is.
Class methods still follow however the variable is declared.
=== begin example run of TestAnimals ===
$ ls
Animal.java Horse.java TestAnimals.java
$ javac *.java
$ java TestAnimals
Animal a=Animal@42847574
Animal b=Horse@63b34ca
Horse c=Horse@1906bcf8
calling a.eat(): Hello from Animal.eat()
calling b.eat(): Hello from Horse.eat()
calling c.eat(): Hello from Horse.eat()
calling a.xyz(): Hello from Animal.xyz()
calling b.xyz(): Hello from Animal.xyz()
calling c.xyz(): Hello from Horse.xyz()
Now a and c point to same instance:
Animal a=Horse@1906bcf8
Horse c=Horse@1906bcf8
calling a.eat(): Hello from Horse.eat()
calling c.eat(): Hello from Horse.eat()
calling a.xyz(): Hello from Animal.xyz()
calling c.xyz(): Hello from Horse.xyz()
$
=== end example run of TestAnimals ===
public class TestAnimals {
public static void main( String [] args ) {
Animal a = new Animal( );
Animal b = new Horse( );
Horse c = new Horse( );
System.out.println("Animal a="+a);
System.out.println("Animal b="+b);
System.out.println("Horse c="+c);
System.out.print("calling a.eat(): "); a.eat();
System.out.print("calling b.eat(): "); b.eat();
System.out.print("calling c.eat(): "); c.eat();
System.out.print("calling a.xyz(): "); a.xyz();
System.out.print("calling b.xyz(): "); b.xyz();
System.out.print("calling c.xyz(): "); c.xyz();
a=c;
System.out.println("Now a and c point to same instance: ");
System.out.println("Animal a="+a);
System.out.println("Horse c="+c);
System.out.print("calling a.eat(): "); a.eat();
System.out.print("calling c.eat(): "); c.eat();
System.out.print("calling a.xyz(): "); a.xyz();
System.out.print("calling c.xyz(): "); c.xyz();
}
}
public class Animal {
public void eat() {
System.out.println("Hello from Animal.eat()");
}
static public void xyz() {
System.out.println("Hello from Animal.xyz()");
}
}
class Horse extends Animal {
public void eat() {
System.out.println("Hello from Horse.eat()");
}
static public void xyz() {
System.out.println("Hello from Horse.xyz()");
}
}