Метод вызывает другой метод, который переопределяется, который вызывается в подклассе?

#java #inheritance #overriding

#java #наследование #переопределение

Вопрос:

Если у меня есть 2 класса, один из которых является родительским со следующим:

 public class Parent {
    ...
    public void method1() {
        method2();
    }

    public void method2() {
    }
}
  

А затем в подклассе

 public class Child extends Parent {
    ...
    public void method2() {
        ...
    }
}
  

Если я выполню следующий код:

 Child c = new Child();
c.method1();
  

Какая версия method2 вызывается?

Комментарии:

1. Почему бы вам не запустить код через отладчик и не включить некоторые println инструкции?

2. Почему бы вам просто быстро не реализовать это в тестовой программе и не выяснить?

3. Напишите обучающий тест и выясните: users.csc.calpoly.edu /~djanzen/tdl/learningtest

4. Тестирование этого дало бы ответ, но не причину. И это, вероятно, то, что запрашивающий хочет знать, даже если он явно не спрашивал об этом. Было бы лучше предложить ознакомиться с руководствами по Java или даже спецификациями, но найти лучшие ресурсы или разобраться в них не всегда так просто для новичка в Java или программировании. Иногда нам приходится читать между строк.

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

Ответ №1:

В Java все методы являются виртуальными, что означает, что это тот Child.method2 , который будет вызван (даже если вызов выполняется из контекста Parent ).

Если корректность Parent.method1 зависит от реализации method2 , вам следует спроектировать его по-другому:

 public class Parent {
    ...
    public void method1() {
        method2impl();
    }

    public void method2() {
        method2impl();
    }

    // make it private or final.
    public final method2impl() {
        ...
    }
}
  

Ответ №2:

Child#method2 будет вызван, поскольку он переопределяет родительский.

Комментарии:

1. Из интереса, является ли обозначение Class#method каким-то соглашением, чтобы прояснить, что это метод экземпляра, а не статический? Потому что это было бы полезно для будущего использования, если это так.

2. Я не знаю, насколько заметна нотация, но, по крайней мере, я так думал 🙂 Я знаю, что это стандарт, по крайней мере, в ruby .

3. Я видел, как он использовался здесь раньше. Это неплохое соглашение, думаю, я тоже начну его использовать.

Ответ №3:

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

Даже если вы вызываете, method1 который был определен в Parent , как только этот метод вызывается method2 , он переходит к реализации типа среды выполнения объекта. Если это Child , то это метод класса, который будет вызван.

Имейте в виду, что это динамическое поведение отличается от выбора метода на основе типов параметров, который выполняется статически. Возьмите следующее, с вашими определениями классов…

 public void methodTest(Parent p) {} //Let's call this "first method"
public void methodTest(Child c) {} //Let's call this "second method"

Parent p = new Parent();
Child c = new Child();

//Assume a is a variable of some type that implements the above methods
a.methodTest(p); //Will call first method
a.methodTest(c); //Will call second method
a.methodTest((Parent)c); //WILL CALL FIRST METHOD!
  

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

Но выбор метода на основе того, к какому объекту он вызывается, зависит от типа среды выполнения этого объекта. Это то, что позволяет нам переопределять поведение метода в подклассах.

Ответ №4:

 public class Parent {
    public void method1() {
        method2();
    }
    public void method2() {
        System.out.println("parent m 2");
    }
}
public class Child extends Parent {
    public void method2(){
        System.out.println("child m 2");
    }
}
public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        c.method1();
        System.out.println("________________");
        c.method2();
    }
}
  

И результат будет:

 child m 2
________________
child m 2
  

Ответ №5:

У меня есть еще несколько вопросов к вам:

 public interface CanDoMethod1 {
    public void method1();
}

public class Parent implements CanDoMethod1 {
    public void method1() {
        System.err.println("Parent doing method1");
    }
}

public class Child extends Parent {
    public void method1() {
        System.err.println("Child doing method1");
    }    
}
  

Теперь вы запускаете следующий код:

 CanDoMethod1 instance = new Parent();
instance.method1();
  

Каков результат?

И когда вы запускаете:

 CanDoMethod1 instance = new Child();
instance.method1();
  

Тогда каков результат?

И когда вы запускаете:

 Parent instance = new Child();
instance.method1();
  

Тогда каков результат?
Почему здесь не требуется приведение?

И когда вы запускаете:

 Child instance = (Child) new Parent();
instance.method1();
  

Компилируется ли это?
Если да, то каков результат?

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