Подтвердить что ты не робот

Вызывается метод суперкласса, хотя объект имеет подкласс

Я играл с простой перегрузкой правил переопределения и нашел что-то интересное. Вот мой код.

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");
    }
}

Эта программа выводит ниже.

еда для животных

Вот что я знаю:

  • Поскольку у нас есть метод private void eat(), он не обязательно будет доступен в подклассе, поэтому вопрос об переопределении метода здесь не возникает как JLS четко определяет его.
  • Теперь, когда это не метод переопределения, он определенно не будет вызывать метод public void eat() из класса Horse
  • Теперь наша декларация Animal a = new Horse(); действительна из-за полиморфизма.

Почему a.eat() вызывает метод из класса Animal? Мы создаем объект Horse, поэтому почему вызов метода класса Animal вызывается?

4b9b3361

Ответ 1

Я не уверен, понимаю ли я ваше замешательство. Основываясь на том, что вы знаете:

Вы правы, Horse.eat() не переопределяет Animal.eat() (поскольку он является закрытым). Другим словом, когда вы вызываете anAnimal.eat(), никакого позднего связывания не происходит, и, следовательно, вы просто вызываете Animal.eat(), что вы видите.


Из вашего другого комментария кажется, что ваше замешательство заключается в том, как компилятор решает, что звонить. Вот очень подробное объяснение:

Когда компилятор видит Animal a =...; a.eat();, он попытается решить, что вызывать.

Например, если он видит, что eat() является статическим методом, данный a является ссылкой на Animal, компилятор переведет его на вызов Animal.eat().

Если это метод экземпляра и он встречает метод, который может быть переопределен дочерним классом, что делает компилятор, он не будет генерировать инструкции для вызова определенного метода. Вместо этого он будет генерировать инструкции для выполнения какого-то поиска с vtable. Понятно, что каждый объект будет иметь небольшую таблицу, ключ которой является сигнатурой метода, а значение является ссылкой на фактический метод вызова. Например, если в вашем случае Animal.eat() не является приватным, то то, что Horse vtable будет содержать, похоже на ["eat()" -> "Horse.eat()"]. Таким образом, при времени выполнения, при задании ссылки Animal и eat() происходит то, что происходит на самом деле: поиск из vtable указанного объекта с помощью eat() и вызов связанного метода. (Если ref указывает на a Horse, связанный с ним метод будет Horse.eat()). В большинстве случаев магия позднего связывания выполняется.

С помощью метода экземпляра, который нельзя переопределить, компиляторы выполняют аналогичные действия как статические методы и генерируют инструкции для прямого вызова этого метода.

(Вышеуказанное не является технически точным, просто концептуальной иллюстрацией для вас, чтобы понять, что произошло)

Ответ 2

Методы, отмеченные private, нельзя переопределять в подклассах, потому что они не видны подклассу. В некотором смысле ваш класс Horse не имеет понятия, что Animal имеет метод eat, так как он помечен private. В результате Java не рассматривает метод Horse eat как переопределение. Это в первую очередь спроектировано как функция безопасности. Если у класса есть метод, он помечен как private, предполагается, что этот метод предполагается использовать только для внутренних элементов класса и полностью недоступен для внешнего мира. Если подкласс может переопределить метод private, он может непредвиденно изменить поведение суперкласса, что (1) не ожидается, и (2) потенциальный риск для безопасности.

Поскольку Java предполагает, что метод private класса не будет переопределен, всякий раз, когда вы вызываете метод private посредством ссылки какого-либо типа, Java всегда будет использовать тип ссылки, чтобы определить, какой метод а не использовать тип объекта, на который указывает эта ссылка, чтобы определить способ вызова. Здесь ссылка имеет тип Animal, так что метод, вызываемый, хотя эта ссылка указывает на a Horse.

Ответ 3

То, что вы, вероятно, не замечаете здесь: ваш основной метод находится в классе Animal. Поэтому нет смысла вызывать частный метод eat() из того же класса. Если вы переместите свой основной метод в другой класс, вы обнаружите, что вызов eat() на Animal приведет к ошибке компилятора!

И, конечно же: если бы вы поставили аннотацию @Override на eat() в Horse, вы тоже получили бы ошибку компилятора. Потому что, как хорошо объяснили другие: вы не отменяете ничего в своем примере.

Итак, по существу:

  • Вы ничего не делали.
  • Вы не вызывали метод, который, по вашему мнению, вы вызывали

Наконец, в отношении вашего комментария: конечно, есть объект Animal. Лошадь расширяется Животное; поэтому любой объект Лошади также является объектом Животных. Вот почему вы смогли записать

Animal a = new Horse();

Но важно понять: после этой строки компилятор больше не знает, что "а" фактически Лошадь. Вы объявили "а" как Животное; и поэтому компилятор позволяет вам вызывать методы, объявляемые Animal. Имейте в виду: наследование в основном описывает отношения "IS-A": в вашем примере - лошадь IS A Animal.

Ответ 4

Короче говоря, вы перегружаете намеченное значение "переопределения" в Java:-).

Предположим, что кто-то написал класс Animal: (переписывая его немного, не изменяя семантики, но демонстрируя хорошую практику). Мы также предположим, что Animal компилируется и работает нормально:

public class Animal {

    public static void main(String args[]) {

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

Это хорошая практика кодирования Java, потому что автор класса Animal не хочет, чтобы вы, читатель этого кода, действительно знали что-либо о частном бизнесе Animal.

Далее вы посмотрите на метод public static void main Animal и правильно сделаете вывод, что там определен метод с именем eat(). На этом этапе выполняется следующее:

  • Как и любой другой класс в Java, Animal extends Object.
  • Вы смотрите на общедоступные (и защищенные) методы Object и обнаружите, что нет такого метода, как eat(). Учитывая, что Animal компилируется отлично, вы можете заключить, что eat ing должен быть Animal частным бизнесом! Нет другого способа, который Animal мог бы скомпилировать. Таким образом, не глядя на Animal частный бизнес, вы можете сделать вывод о том, что существует eat() метод в Animal классе, который является приватным!

Теперь скажите, что ваше намерение состояло в том, чтобы создать другое животное с именем Horse в качестве специализированного Animal и придать ему особое поведение. Вы считаете, что не собираетесь изучать Java Lang Spec и выяснять все правила этого и просто использовать ключевое слово extends и делать с ним. Затем появляется первая версия Horse. Вы где-то слышали, что лучше уточнить свое намерение переопределить (это одно из того, что вы сейчас уверены - вы хотите переопределить поведение eat Horse):

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

справа; вы добавляете тег @Override. Это всегда хорошая идея, по общему признанию, при увеличенной формулировке (это хорошая практика по нескольким причинам, что мы не будем здесь входить).

Вы пытаетесь скомпилировать Horse.java, и вы видите:

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

Таким образом, компилятор, который знает язык программирования Java лучше нас, говорит нам, что мы, по сути, не переопределяем и не реализуем метод, объявленный в супертипе.

Теперь обработка переопределения в Java становится понятной для нас. Поскольку мы должны только переопределять это поведение, предназначенное для переопределения, а именно публичные и защищенные методы, мы должны быть осторожны в том, как написаны суперклассы. В этом случае непреднамеренно суперкласс Animal, который, по-видимому, был разработан для расширения, не позволял подклассам переопределять поведение eat!

Даже если мы удалили тег @Override, чтобы избавиться от технических характеристик ошибки/предупреждения компилятора, мы не обязательно будем поступать правильно, потому что во время выполнения, как вы заметили, непреднамеренный метод, подпись которого совпадает, вызывается. Это еще хуже.

Ответ 5

Частные методы не могут быть отменены.

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();

    }
}

Ответ 6

Вы писали:

Animal a = new Horse();

В этой ситуации a указывает на объект Лошадь, как если бы это был объект Animal.

a.eat() является закрытым в классе Animal, поэтому его нельзя переопределить, и поэтому a.eat() происходит от Animal