3

I am currently reading a book and stuck at following code:

public class TestAnimals {
    public static void main (String [] args ) {
        Animal a = new Animal();
        Animal b = new Horse();

        a.eat(); // Runs the Animal version of eat()
        b.eat(); // Runs the Horse version of eat()

    }
}

class Animal {
    public void eat() {
        System.out.println("Generic animal eating generically");
    }
}

class Horse extends Animal {
    public void eat() {
        System.out.println("Horse eating hay, oats, horse treats");
    }

    public void buck() {
    }
}

Please have a look at the commented lines.

The book goes on to say "To reiterate, the compiler looks only at the reference type, not the instance type". Really? Had this been the case, both a.eat() and b.eat() would produce same result, as they (a and b) have same reference type (which is Animal).

Also to me this seems to be the compile time binding because virtual keyword has not been used but in the book results are of run time binding. I am so confused at this point. Any help would be greatly appreciated.

Ravindra babu
  • 37,698
  • 11
  • 250
  • 211
  • Possible duplicated [http://stackoverflow.com/questions/2055840/difference-between-load-time-dynamic-linking-and-run-time-dynamic-linking](http://stackoverflow.com/questions/2055840/difference-between-load-time-dynamic-linking-and-run-time-dynamic-linking) – Abdelhak Jan 13 '16 at 17:40
  • Possible duplicate of [Compile time vs Run time Dependency - Java](http://stackoverflow.com/questions/4270950/compile-time-vs-run-time-dependency-java) – Zia Jan 13 '16 at 17:46
  • 1
    Are the `//RESULT` comments in the book too? In that case the text contradicts the comments. Now, if the methods were `static` then I do believe both the calls will execute the `superclass method` – Miserable Variable Jan 13 '16 at 17:54
  • @MiserableVariable Updated to exact book comments. Yes, that is my question. – Sandeep Gupta Jan 13 '16 at 18:10
  • 1
    I think the ambiguity is about what the compiler is looking for 2h3n i5 looks only at reference type. As @Theodoros explains in his answer the book is referring to checking the call compatibility which is indeed done with reference type. It may further help you to look at the `javap` output for the class to understand. – Miserable Variable Jan 13 '16 at 18:30

3 Answers3

1

The compiler does indeed look only at the statically known type, not the actual runtime type of the instance - after all, Java is a statically typed language. As a matter of fact, in all but the most trivial cases, the compiler can't even know the runtime type of an object reference (as to solve this problem for the general case, it would have to solve undecidable problems).

The point the book is trying to make there is that this snippet would fail to compile:

b.buck();

Because b is of (compile-time) type Animal and Animal doesn't have a buck() method. In other words, Java (like C++), will verify at compile time whether the method call makes any sense, based on the information it has about the type of the variable.

Now the reason the book's results correspond to runtime binding is precisely because you have runtime binding at that call spot: in Java (unlike in C++), all non-static methods are virtual by default.

Thus, there's no need for a virtual keyword that would allow you to explicitly opt in to polymorphism semantics (as you would in C++ and C#, for example). Instead, you can only prevent any further overrides of your methods by individually marking them as final or marking their containing class as final (if the latter is applicable in your case).

Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • 1
    Strictly speaking, `final` doesn't make a method non-virtual, but rather prevents further overrides. A method can be overridden and declared final, and then further overrides are impossible, but all previous overrides up to the final one will work just as fine. You could just as well said that you can opt out of virtualness by making a method `final`, but then again it's another thing. There are just no non-virtual methods in Java. – Sergei Tachenov Jan 13 '16 at 17:46
  • **in Java, all non-static methods are virtual by default.** Thank you for your response. I just switched from C++ and therefore encountering such problems. Does this mean that there is nothing called compile time linking in Java? – Sandeep Gupta Jan 13 '16 at 17:57
  • @SandeepGupta Calls to `static` methods are statically resolved, as far as I know. See helper classes like `Math`. Though I'd personally call it static *binding*, as I reserve the term *linking* for linking libraries (in the C and C++ sense). – Theodoros Chatzigiannakis Jan 13 '16 at 18:01
  • I really apologize that I did not ask this in my previous comment. 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? There seems to be a contradiction. – Sandeep Gupta Jan 13 '16 at 18:08
  • @SandeepGupta I've edited the answer a bit, to include that. Basically, Java will still give you compile time errors (like C++ would) when you have a reference variable and attempt to call on it a method that the reference type doesn't have (even if the runtime type has it). This is what the book means when it says that the compiler looks at the reference type. It means that the *error checking* happens with the reference type in mind. After verifying (at compile time) that the call itself makes sense, the actual selection of the correct override is the only thing that's left to the runtime. – Theodoros Chatzigiannakis Jan 13 '16 at 18:13
  • @TheodorosChatzigiannakis Now it makes sense. Compile time binding for error checking and runtime for subsequent part (as all non-static methods are virtual by default). I would have up voted your answer but it require some minimal points with you. Thank you so much, anyways. – Sandeep Gupta Jan 13 '16 at 18:24
  • @SandeepGupta Not a problem! I might only suggest that you consider marking it as accepted, if you believe that it covers your question fully. – Theodoros Chatzigiannakis Jan 13 '16 at 18:32
  • 1
    @SandeepGupta, I'd also add that compile time binding affects not only error checking, but also type inference and overload resolution. If there are multiple methods with different signatures, then there is a rather arcane algorithm that selects the method to call (or reports an error if the call is ambiguous) and it uses compile time binding as well. – Sergei Tachenov Jan 14 '16 at 06:25
1

@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()");
   }
}
jgreve
  • 1,225
  • 12
  • 17
0

This question can be re-phrased as difference between Static binding & Dynamic binding.

  1. Static binding is resolved at compile time and Dynamic binding is resolved at run time.
  2. Static binding uses type of "Class" (reference as per your example) and Dynamic binding uses type of "Object" (instance as per your example). private, final ,static methods are resolved at compile time.

  3. Method overloadingis an example ofStatic binding&Method overridingis example ofDynamic binding`.

In your example,

Animal b = new Horse();
b.eat();

resolution of Object on which "eat()" method has to be invoked happens at runtime for Animal b. During runtime, Animal b has been resolved to Horse type and Horse version of eat() method has been invoked.

Have a look at this article for better understanding.

Have a look at related SE question : Polymorphism vs Overriding vs Overloading

Community
  • 1
  • 1
Ravindra babu
  • 37,698
  • 11
  • 250
  • 211