2012-02-03 2 views
11

Класс A вызывает открытый метод f() в конструкторе. Класс B отменяет метод f() с его собственной реализацией.Полиморфный метод в конструкторе (Java)

Предположим, вы intantiate объект B .. метод f() из объекта B будет называться в Застройщиком объекта A, хотя объект B не полностью инициализирован.

Может ли кто-нибудь объяснить это поведение?

EDIT: Да, это не рекомендуется практика .. пока я не понимаю, почему Java не вызывая f() реализацию базового класса A вместо «охвата» в f() реализации от производного класса B.

Код:

class A { 
    A() { 
     System.out.println("A: constructor"); 
     f(); 
    } 

    public void f() { 
     System.out.println("A: f()"); 
    } 
} 

class B extends A { 
    int x = 10; 
    B() { 
     System.out.println("B: constructor"); 
    } 

    @Override 
    public void f() { 
     System.out.println("B: f()"); 
     this.x++; 
     System.out.println("B: x = " + x); 

    } 
} 

public class PolyMethodConst { 
    public static void main(String[] args) { 
     new B(); 
    } 
} 

Выход:

A: constructor 
B: f() 
B: x = 1 
B: constructor 
+0

Что вы хотите объяснить? Вы выполнили 'B.f' перед [основной] конструктором' B' (C++ запускал 'A.f', несмотря на то, что он был переопределен). –

+0

Сравнение с C++ довольно интересно. На «безопасном» управляемом языке было принято решение сделать эту операцию небезопасной (ну, программа не будет разбиваться, хотя NPE возможен, но результат непредсказуем и непредсказуем, так как зависит от производного класса). – vehsakul

ответ

8

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

+1

+1 - это не очень хорошая практика, потому что объект не находится в хорошем состоянии. –

+2

Как правило, любой метод, вызываемый из конструктора, должен быть либо конечным, либо частным. Качание особенно плохо об этом. Наследование от класса Swing и попытка переопределить setFont были довольно приключением. –

0

Я просто предоставил ссылку, так как я не очень хорошо информирован по этому вопросу. См. here для учебника, в котором рассказывается о порядке вызова конструкторов.

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

  1. Конструктор базового класса вызывается. Этот шаг повторяется рекурсивно, так что сначала создается корень иерархии, , за которым следует следующий производный класс и т. Д., Пока не будет достигнут наиболее производный класс .
  2. Инициализаторы членов вызываются в порядке объявления. Вызывается тело конструктора производного класса.

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

Но, как упоминалось выше, это не очень хорошая практика. Следуйте тому, что говорит Билл. У него больше репутации, чем у меня.

EDIT: Для получения более полного ответа см. this Jon Skeet answer. Ссылка в этом ответе сломана, и только PDF-копию JLS можно найти AFAIK. Here - это копия JLS в формате .pdf. Соответствующий раздел - Раздел 8.8.7.1. Существует объяснение того, какой порядок вызова конструктора находится в этом ответе.

4

Всякий раз, когда вы создаете экземпляр подкласса, сначала запускается конструктор суперклассов (неявный super()). Так печатает

a: constructor 

f() вызывается рядом и поскольку подкласс переопределяет метод суперкласса, подкласс f() вызывается. Таким образом, вы будете видеть

B: f() 

Теперь подкласс не инициализированы (все еще супер() выполняет) так x по умолчанию к значениям 0, потому что это значение по умолчанию для типа int. Потому что увеличивается его (this.x++;) становится 1

B: x = 1 

Теперь конструктор суперкласса завершена и продолжается в конструкторе подклассы и, следовательно,

B: constructor 

Переменные экземпляра теперь устанавливаются значения, которые они имеют (по значениям по умолчанию, которые соответствуют типу (0 для цифр, false для boolean и null для справок))

ПРИМЕЧАНИЕ: Если теперь напечатать значение x на вновь созданный объект, то он будет 10

Поскольку это плохая практика, инструменты статического анализа кода (PMD, FindBugs и т.д.) предупредить вас, если вы попробуйте сделать это.

0

Когда new B(), конструктор A называется неявным или называется через super(). Хотя он определен в классе A, на самом деле текущий класс равен B.

Попробуйте добавить информацию об отладке ниже в конструктор и функции A.

System.out.println(this.getClass()); 

В вашем случае функция е() в классе А был перекрываться класса B, поэтому функция A() будем называть B() 'ы реализации. Однако, если f() является частным методом и не может быть переопределен B, A.f() будет вызываться с более высокими приоритетами.

Но, как прокомментировали другие, это не очень хорошая практика.

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