2015-09-08 2 views
2

Учитывая Main.java:Trouble пытается понять наследование

public class Main{ 
    public static void main(String[]args){ 
      A a = new B(); 
      a.print(); 
    } 

} 

class A{ 
     A() {print();} 
     void print() { System.out.println("A"); } 
} 

class B extends A{ 
     int i = 4; 
     void print() { System.out.println(i); } 
} 

Результаты в:
4.
Но почему не a.print выход "А", если он ссылается класса А? Как я узнаю, когда один метод будет вызван в другом случае в таких случаях? Почему конструктор A вызывается и все еще использует метод B?

+0

'a.print' не ссылается на класс A, a является просто объектом типа' A' со ссылкой на тип 'B'. – Geinmachi

+0

Это отличный пример того, почему мы должны избегать полиморфных методов внутри конструкторов. Чтобы избежать таких проблем, используйте только те методы, которые являются частными, статическими или окончательными. – Pshemo

ответ

5

Адрес a.print() Отпечатки 4 по причине polymorphism. Вызываемый метод зависит от типа времени выполнения a, который равен B. Неважно, когда это называется; полиморфизм применяется всегда.

Оба раза, метод называется. Один раз из конструктора A, который вызывается конструктором по умолчанию в B. В другой раз ваш явный звонок в main.

Причина, по которой первые печатные выходы 0, а не 4 происходит потому, что в то время, что называется print, A все еще строится. То есть, конструктор A все еще выполняется. Прежде чем конструктор суперкласса вернется, в подклассе еще ничего не инициализируется, даже переменные инициализаторы. Значение 4 назначается после завершения конструктора суперкласса, но до того, как завершится работа над конструктором подкласса. Поскольку инициализаторы переменных еще не запущены, значение по умолчанию 0 (это будет false для boolean и null для объектов) - это значение i в первой печати.

Этот порядок перечисленное JLS, Section 12.5:

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

  1. Назначьте аргументы для конструктора вновь созданным переменным параметра для этого вызова конструктора.

  2. Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (с использованием этого), затем оценивайте аргументы и обрабатывайте вызов конструктора рекурсивно, используя эти пять шагов. Если вызов конструктора завершается внезапно, то эта процедура завершается внезапно по той же причине; в противном случае перейдите к шагу 5.

  3. Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (с использованием этого). Если этот конструктор относится к классу, отличному от Object, то этот конструктор начнет с явного или неявного вызова конструктора суперкласса (используя super). Оцените аргументы и обработайте вызов конструктора суперкласса рекурсивно, используя эти пять шагов. Если вызов конструктора завершается внезапно, то эта процедура завершается внезапно по той же причине. В противном случае перейдите к шагу 4.

  4. выполнять экземпляр инициализаторы и переменные экземпляр инициализаторы для этого класса, присваивая значения переменного экземпляра инициализаторов в соответствующем переменный экземпляре, в слева-направо порядке, в котором они появляются текстуальны в исходном коде для класс. Если выполнение любого из этих инициализаторов приводит к исключению, то никакие новые инициализаторы не обрабатываются, и эта процедура завершается внезапно с тем же исключением. В противном случае перейдите к шагу 5.

  5. Выполнение остальной части тела этого конструктора. Если это выполнение завершается внезапно, то эта процедура завершается внезапно по той же причине. В противном случае эта процедура завершится нормально.

(жирный курсив мой)

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

1

В случае переопределены методы (например, печать() в вашем примере), объект типа решает, какой метод должны быть вызван, а не опорного типа.

0

B не имеет конструктора, поэтому его конструктор по умолчанию ничего не сделает, кроме вызова конструктора A.

Теперь, когда вызывается конструктор по умолчанию B, он вызывает конструктор A (помните, что i все еще не установлен, поэтому значение по умолчанию 0). Конструктор A вызывает print(), теперь, поскольку объект фактически B, он называет печать B() и печатает 0 (помните, что i не был установлен).

Теперь, как только этот вызов конструктора завершает код B получает Выполняет который устанавливает я 0. Теперь вызов печати() будут снова получить вам печать Б() (как объект только B), который будет печатать 4.

«DEBUGGING THE KEY»

Смежные вопросы