Кажется, не понимают сложный полиморфизм

Я изучаю CS, и у нас есть вопросы о полиморфизме, которые я не могу обмануть. Вот пример:

public class AA{
    public AA(){
        foo();
    }
    private void foo() {
        System.out.print("AA::foo ");
        goo();
    }
    public void goo(){
        System.out.print("AA::goo ");
    }
}

public class BB extends AA{
    public BB(){
        foo();
    }
    public void foo(){
        System.out.print("BB:foo ");
    }
    public void goo(){
        System.out.print("BB::goo ");
    }
    public static void main(String[] args){
        // Code goes here
    }
}

Когда в void main я добавляю строку:

AA a = new BB();

сначала идет AA-конструктор AA: foo, но затем goo() отправляет его в BB goo, почему так?

Простой полиморфизм, такой как "Animal → cat/spider/dog", легко понять, но когда дело доходит до этого, я просто потерялся. Можете ли вы, ребята, дать мне какие-нибудь советы, как читать этот код? Каковы правила?

EDIT: нет аннотации @Override потому что это вопрос с экзамена.

Ответы

Ответ 1

объяснение

public class AA {

    private void foo() { ... }
    ^^^^^^^

}

Полиморфизм не применяется к private методам. Подкласс не наследует private методы, поэтому их нельзя переопределить:

Класс C наследует от своего прямого суперкласса все конкретные методы m (как статические, так и экземпляры) суперкласса, для которых выполняются все следующие условия:

  • m является членом прямого суперкласса C
  • m является public, protected или объявленным с доступом к пакету в том же пакете, что и C
  • Никакой метод, объявленный в C имеет подписи, которая является поднаклейкой сигнатуры m.

Спецификация языка Java - 8.4.8.Наследование, переопределение и скрытие

Поэтому вызов foo() из конструктора A не вызывает BB#foo, он вызывает AA#foo.

Но вызов goo() внутри AA#foo относится к переопределенному методу BB#goo. Здесь были применены public методы, переопределение методов и полиморфизм.


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

public class BB extends AA {

    @Override   // it doesn't compile - no overriding here
    public void foo() { ... }
    @Override   // it does override
    public void goo() { ... }

}

Также может быть полезно обнаружить еще одну проблему:

Программисты иногда перегружают объявление метода, когда они хотят переопределить его, что приводит к тонким проблемам. Тип аннотации Override поддерживает раннее обнаружение таких проблем.

Если объявление метода в типе T аннотируется с помощью @Override, но метод не отменяет из T метод, объявленный в супертипе T, или не переопределяет эквивалент общедоступному методу Object, тогда возникает ошибка времени компиляции,

Спецификация языка Java - 9.6.4.4. @Override

иллюстрация

Если тело конструктора не начинается с явного вызова конструктора и объявляемый конструктор не является частью первичного класса Object, то тело конструктора неявно начинается с вызова суперкласса super(); , вызов конструктора его прямого суперкласса, который не принимает аргументов.

Спецификация языка Java - 8.8.7.Тело конструктора

Проще говоря,

public BB() {
    foo();
}

превращается в

public BB() {
    super();
    foo();
}

Сохранение super(); в виду, мы можем сделать следующую иллюстрацию:

new BB()
        AA()                       // super(); -> AA constructor
                A#foo()            // private method call 
                        B#goo()    // polymorphic method call
        BB()                       // BB constructor
                B#foo()            // plain method call 

Ответ 2

Это очень хорошо объяснялось в официальных документах:

https://docs.oracle.com/javase/tutorial/java/IandI/super.html

Если конструктор явно не вызывает конструктор суперкласса, компилятор Java автоматически вставляет вызов конструктору без аргументов суперкласса. Если у суперкласса нет конструктора без аргументов, вы получите ошибку времени компиляции. У объекта есть такой конструктор, поэтому, если Object является единственным суперклассом, проблем нет.

Итак, компилятор Java добавляет super() без аргументов для вас.

На самом деле, если класс, который вы расширяете, не имеет конструктора по умолчанию, вам нужно будет вызвать этот конструктор с аргументами раньше.


В противном случае причина, по которой AA:goo не вызывается, - это переопределение BB даже если у него нет аннотации @Override, если вы хотите увидеть этот вызов, вам нужно использовать super(); в вашем методе b: goo. На самом деле, foo не переопределяется, потому что он закрыт, поэтому его невозможно переопределить, если вы попытаетесь добавить аннотацию @Override, у вас возникнет сбой компиляции.

Ответ 3

AA :: foo() является приватным, поэтому AA :: foo() и BB :: foo() не совпадают.

new BB()

сначала вызовет AA :: Constructor, вызвав AA :: foo().

AA :: foo() call goo(), и поскольку вы создали экземпляр класса BB, это будет BB :: goo().

Используйте "переопределить" ключевое слово в методе, когда хотите сделать что-то вроде этого

Ответ 4

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

public class AA{

    public AA(){
        foo();
    }

    private void foo() {
        System.out.print("AA::foo ");
        goo();
    }

    public void goo(){
        System.out.print("AA::goo ");
    }
}

public class BB extends AA{

    private Date timestamp;

    public BB() {
        super();
        foo();
        timestamp = new Date();
    } 

    public void foo() {
        System.out.print("BB:foo ");
    }

    public void goo() {
        // goo() gets called before timestamp is initialized 
        // causing a NullPointerException
        System.out.print("BB::goo " + timestamp.getYear());
    }

    public static void main(String[] args){
        AA obj = new BB();
    }
}

Помните об этом: НИКОГДА НЕ ВЫЗВАЙТЕ ОБЫЧНЫЙ МЕТОД ОТ КОНСТРУКТОРА (даже не косвенно, как в этом примере)

Ответ 5

Полиморфизм - это Простой, но сбивающий с толку время от времени, когда мы используем другой набор имен [что здесь имеет место].

Полиморфизм в основном является родительским отношением к детям. Ключ здесь, если вы пытаетесь поместить имена классов, используйте сами, а вместо этого дайте комментарий рядом с именами классов, как показано ниже.

public class AA{} //your Parent name
public class BB extends AA{} // yourself i.e. your name

Когда дело доходит до такого кода, AA a = new BB(); , декодируйте код, как показано ниже:

BB - это вы, АА - ваш родитель.

new ключевое слово с YOU (т.е. BB), поэтому новый объект ВАС будет создан или рожден. Для того, чтобы ВАС родиться, без ваших родителей (то есть АА), вы не можете существовать, и поэтому сначала они будут созданы или созданы (т.е. Будет выполняться конструктор АА). Как только ваши родители (т.е. АА) создаются, тогда настало время для ВАС родиться (т.е. Конструктор BB будет работать).

В вашем примере,

public AA(){
        foo(); -- line A
    }
    private void foo() {
        System.out.print("AA::foo ");
        goo(); -- line B
    }
    public void goo(){
        System.out.print("AA::goo "); -- line C
    }

Как я сказал ранее, Line A будет вызываться, когда вы скажете AA a = new BB(); поскольку строка A находится в конструкторе AA, линия A вызывает метод foo(), и поэтому элемент управления помещается в foo(), печатает "AA :: foo" и выполняет строку B. Строка B вызывает метод goo() и так далее, она достигает линии C. После выполнения строки C в конструкторе АА нет ничего, что было бы выполнено (т.е. Объект создан), и поэтому управление переходит к дочернему конструктору (при создании родителя, пришло время для рождения ребенка), и поэтому ребенок Конструктор будет называться далее.

Для студентов/начинающих я настоятельно рекомендую пройти Head First Java Edition. Это действительно помогает вам в создании Java Foundation Strong.