2016-08-22 5 views
27

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

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?

+0

Ну, не вы объяснить это чуть выше. Объявленный тип '' 'Animal, а не Horse, и Animal.eat() является закрытым, поэтому его нельзя переопределить, поэтому его нельзя назвать полиморфно. Метод подкласса будет вызываться, если он переопределяет класс базового метода, но он этого не делает, поэтому ... –

+1

@ JBNizet, да, но они почему метод еды класса животных называют, что я не могу понять. Это потому, что у нас есть ссылка типа класса Animal? Но у нас есть объект типа Horse. то почему он вызывает метод класса Animal? –

+4

Весь компилятор знает о 'a' о том, что это Animal: это его объявленный тип. Таким образом, компилятор ищет метод eat() в Animal. Он находит один, и он частный, поэтому он называет этот метод. И поскольку его нельзя переопределить, вызывается Animal.eat(). –

ответ

14

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

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

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

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

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

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

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

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

+0

спасибо, я получил его сейчас :) Время изучать методы привязки :) –

+2

thats очень хорошее объяснение :) большое спасибо. Сегодня хороший день, мне нужно научиться –

+0

исправьте меня. Если я ошибаюсь, в основном 'метод' получает флаг пропуска из компилятора (время компиляции), и поскольку нет ошибки времени выполнения, поэтому все (** модификаторы доступа **) игнорируются при время выполнения и, следовательно, происходит простой вызов метода? и 'eat()' является private в 'Animal', поэтому выполнение начинается с' Animal'? – emotionlessbananas

24

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

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

+1

Большое спасибо за ваш ответ. Я понял, что частные методы не переопределены и что я уже упоминал сам вопрос. Лошадиный метод не будет вызван, потому что он не переопределяет. Мой единственный вопрос - почему метод класса «Животные» вызывается, хотя это объект класса «Лошадь». –

+0

@PrasadKharkar Я думаю, вы можете взглянуть на то, как достигается поздняя привязка к методу (по v-таблице). Для понимания поведения должно быть достаточно понимания высокого уровня. –

+0

@PrasadKharkar. Кажется, я вижу, о чем вы смущены. Я обновил свой ответ соответственно - дайте мне знать, если у вас остались вопросы! – templatetypedef

0

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

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

    } 
} 
+2

Код здесь кажется очень похожим на код в вопросе. Есть ли что-то в этом роде? – templatetypedef

+0

Нет. Я не хотел показывать ошибку, которую компилятор java возвращает, говоря, что метод частного приема не может быть переоценен. –

+0

@templatetypedef 'main()' находится в другом классе – emotionlessbananas

8

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

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

Так, в сущности:

  1. Вы не переопределяя ничего
  2. Вы не были вызовом метода вы думали, называли

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

Animal a = new Horse(); 

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

+0

Да, это правда, и я это понял. Моя единственная путаница в том, что даже если нет объекта Animal, метод еды класса животных зовется. Это мое сомнение. Вызывается метод класса Animal только потому, что у нас есть ссылочная переменная типа Animal? –

+0

спасибо, получили сомнения очищены. Действительно ценю это. –

3

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

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

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 
} 

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

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

  1. Как и любой другой класс в Java, Animal расширяет Object.
  2. Вы смотрите на общедоступные (и защищенные) методы Object и обнаружите, что нет такого метода, как eat(). Учитывая, что Animal компилирует штраф, вы можете заключить, что eat ing должен быть Animal 's частный бизнес! Нет другого способа, который мог бы скомпилировать 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"); 
    } 
} 

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

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

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

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

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

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

+0

Это хороший ответ Кедар, спасибо большое –

0

Вы писали:

Animal a = new Horse(); 

В этой ситуации указывает на Лошадь объекта, если он является объектом животных типа.

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