简体   繁体   中英

Superclass method being called even though object is of subclass

I was playing around with simple overloading overriding rules and found something interesting. Here is my code.

package com.demo;

public class Animal {

    private void eat() {
        System.out.println("animal eating");
    }

    public static void main(String args[]) {

        Animal a = new Horse();
        a.eat();
    }
}

class Horse extends Animal {
    public void eat() {
        System.out.println("Horse eating");
    }
}

This program outputs the below.

animal eating

Here is what I know:

  • As we have private void eat() method, it is not definitely going to be accessed in a subclass, so the question of method overriding does not arise here as JLS defines it clearly.
  • Now that this is not method overriding, it is definitely not going to call public void eat() method from the Horse class
  • Now our declaration Animal a = new Horse(); is valid because of polymorphism.

Why is a.eat() invoking a method from the Animal class? We are creating a Horse object, so why does the Animal class' method get called?

Methods marked private can't be overridden in subclasses because they're not visible to the subclass. In a sense, your Horse class has no idea whatsoever that Animal has an eat method, since it's marked private . As a result, Java doesn't consider the Horse 's eat method to be an override. This is primarily designed as a safety feature. If a class has a method it's marked private , the assumption is that that method is supposed to be used for the class internals only and that it's totally inaccessible to the outside world. If a subclass can override a private method, then it could potentially change the behavior of a superclass in an unexpected way, which is (1) not expected and (2) a potential security risk.

Because Java assumes that a private method of a class won't be overridden, whenever you call a private method through a reference of some type, Java will always use the type of the reference to determine which method to call, rather than using the type of the object pointed at by that reference to determine the method to call. Here, the reference is of type Animal , so that's the method that gets called, even though that reference points at a Horse .

I am not sure if I understand your confusion. Based on what you know:

You are right, Horse.eat() is not overriding Animal.eat() (as it is private). In another word, when you call anAnimal.eat() , no late-binding happens and hence, you are simply calling Animal.eat() , which is what you see.


From your other comment, seems that your confusion is how the compiler is deciding what to call. Here is a very high-level explanation:

When compiler sees Animal a =...; a.eat(); Animal a =...; a.eat(); , it will try to resolve what to call.

For example, if it sees eat() is a static method, given a is a reference to Animal , compiler will translate it to call Animal.eat() .

If it is an instance method, and it encounters a method that may have been overridden by a child class, what the compiler does is, it will not generate instructions to call a specific method. Instead, it will generate instructions to do some kind of lookup from a vtable. Conceptually, each object will have a little table, which the key is the method signature, and the value is the reference to the actual method to call. For example, if in your case, Animal.eat() is not private, what Horse's vtable will contain is something like ["eat()" -> "Horse.eat()"] . So at runtime , given an Animal reference and eat() is called, what happen is actually: lookup from the vtable of the referred object with eat() , and call the method associated. (If the ref is pointing at a Horse , the method associated will be Horse.eat() ). This is how the magic of late binding is done in most cases.

With an instance method that is not possible to be overridden, compilers do similar things as static methods and generate instructions to call that method directly.

(The above is not technically accurate, just a conceptual illustration for you to understand what happened)

The thing that you are probably overlooking here: your main method is within the Animal class. Therefore it is no problem to call the private method eat() from the same class. If you move your main method into another class, you will find that calling eat() on an Animal will then lead to a compiler error!

And of course: if you had put the @Override annotation on eat() within Horse, you would have received a compiler error, too. Because, as other have nicely explained: you are not overriding anything in your example.

So, in essence:

  1. You were not overriding anything
  2. You were not calling the method you thought you were calling

Finally, regarding your comment: of course there is an Animal object. Horse is extending Animal; so any Horse-object is also an Animal object. That is why you were able to write down

Animal a = new Horse();

But the important thing to understand: after that line, the compiler doesn't know any more that "a" is actually a Horse. You declared "a" as Animal; and therefore the compiler allows you to call methods that Animal declares. Keep in mind: inheritance is basically about describing an "IS-A" relationship: in your example, a Horse IS A Animal.

In short, you are overloading the intended meaning of "overriding" in Java :-).

Let's pretend that someone else wrote the Animal class: (rewriting it slightly, without changing any semantics, but to demonstrate a good practice). We will also assume that Animal compiles and runs fine:

public class Animal {

    public static void main(String args[]) {

        Animal a = new Animal(); // yes, Animal, no Horse yet.
        a.eat();
    }
    ///// Animal's private methods, you should not look here
    private void eat() {
        System.out.println("animal eating");
    }
    ///// Animal's private methods, you should not look here
}

This is a good Java coding practice because the author of Animal class doesn't want you, the reader of that code, to really know anything about Animal 's private business.

Next, you look at the public static void main method of Animal and correctly infer that there is a method named eat() defined there. At this point, following holds:

  1. Like any other class in Java, Animal extends Object .
  2. You look at the public (and protected) methods of Object and find that there is no such method as eat() . Given that Animal compiles fine, you can infer that eat ing must be Animal 's private business! There's no other way that Animal could compile. Thus, without looking at Animal 's private business, you could infer that there is an eat() method in Animal class that is private !

Now let's say that your intent was to create another animal named Horse as a specialized Animal and give it a special behavior of eating. You figure that you are not going to look into Java Lang Spec and find out all the rules of doing so and just use the extends keyword and be done with it. The first version of Horse then emerges. You have heard somewhere however that it is better to clarify your intent of overriding (this is one thing you are now certain -- you do want to override eat ing behavior of Horse ):

class Horse extends Animal {
    @Override
    public void eat() {
        System.out.println("Horse eating");
    }
}

Right; you add the tag @Override . This is always a good idea, admittedly, at an increased verbiage (It is a good practice for a few reasons that we'll not go into here).

You try to compile Horse.java and you see:

Error:(21, 5) java: method does not override or implement a 
method from a supertype

Thus, the compiler that knows the Java programming language better than us, tells us that we are, in fact, not overriding or implementing a method that is declared in a supertype .

Now the handling of overriding in Java becomes clearer to us. Since we are supposed to only override that behavior that is designed for overriding, namely the public and protected methods, we should be careful about how the superclasses are written. In this case, inadvertently, the superclass Animal , which was apparently designed for extension, made it impossible for the subclasses to override the eat ing behavior!

Even if we removed the @Override tag to get rid of the technicality of the compiler error/warning, we wouldn't necessarily be doing the right thing because at runtime, as you observed, the unintended method whose signature matches, gets called. That is even worse.

Private Methods Cannot Be Overridden.

http://ideone.com/kvyngL

/* package whatever; // don't place package name! */

import java.util.*;
import java.lang.*;
import java.io.*;

class Animal {

    private void eat() {
        System.out.println("animal eating");
    }
}

class Horse extends Animal {
    public void eat() {
        System.out.println("Horse eating");
    }
}
/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        // your code goes here
                Animal a = new Horse();
        a.eat();

    }
}

You wrote:

Animal a = new Horse();

In this situation a points to Horse object as if it is an object of Animal type.

a.eat() is private in Animal class, so it cannot be overridden, and that's why a.eat() comes from Animal

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM