Ответ 1
Вы правы, так оно и работает. Однако это не рекомендуется, потому что кто-то, кто наследует ваш класс, может непреднамеренно разбить его.
Класс A
вызывает открытый конструктор f()
в конструкторе. Класс B переопределяет метод f()
с его собственной реализацией.
Предположим, вы intantiate Object B
.. метод f()
объекта B
будет вызываться в конструкторе объекта A
, хотя Object 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
Вы правы, так оно и работает. Однако это не рекомендуется, потому что кто-то, кто наследует ваш класс, может непреднамеренно разбить его.
Всякий раз, когда вы создаете экземпляр подкласса, сначала запускается конструктор суперклассов (неявный super()
). Поэтому он печатает
a: constructor
f()
вызывается следующим образом, а так как подкласс переопределяет метод суперкласса, вызывается подкласс f()
. Итак, вы увидите
B: f()
Теперь подкласс еще не инициализирован (все еще выполняется super()), поэтому x
по умолчанию значение 0
, потому что это значение по умолчанию для типа int
. Поскольку вы увеличили его (this.x++;
), он становится 1
B: x = 1
Теперь конструктор суперкласса завершен и возобновляется в конструкторе подклассов и, следовательно,
B: constructor
Теперь переменные экземпляра теперь установлены на значения, которые вы указали (против значений по умолчанию, соответствующих типу (0
для численных значений, false
для boolean
и null
для ссылок))
ПРИМЕЧАНИЕ. Если теперь вы напечатаете значение x
для вновь созданного объекта, оно будет 10
Так как это плохая практика, инструменты статического анализа кода (PMD, FIndBugs и т.д.) предупреждают вас, если вы попытаетесь это сделать.
Я просто предоставил ссылку, так как я не очень хорошо информирован по этому вопросу. См. здесь для учебника, в котором рассказывается о порядке вызова конструкторов.
Наиболее заметной цитатой в конце страницы, связанной с ситуацией, которую вы описываете, является следующее:
- Вызывается конструктор базового класса. Этот шаг повторяется рекурсивно, так что сначала формируется корень иерархии, а затем следующий производный класс и т.д. до тех пор, пока наиболее производный класс.
- Инициализаторы членов вызываются в порядке объявления. Вызывается тело конструктора производного класса.
Итак, как вы показали в своем примере, базовый класс инициализируется, затем каждый из следующих классов инициализируется и, наконец, инициализируются переменные-члены.
Но как упоминалось в Билле, это не очень хорошая практика. Следуйте тому, что говорит Билл. У него больше репутации, чем у меня.
EDIT. Для получения более полного ответа см. этот ответ Jon Skeet. Ссылка в этом ответе сломана, и можно найти только PDF-копию JLS AFAIK. Здесь - копия JLS в формате .pdf. Соответствующий раздел - Раздел 8.8.7.1. Существует объяснение того, какой порядок вызова конструктора находится в этом ответе.
Когда new B()
, конструктор называется неявным или называется через super()
. Хотя он определен в классе A, на самом деле текущий класс равен B.
Попробуйте добавить следующую информацию об отладке в конструктор и функции A
.
System.out.println(this.getClass());
В вашем случае функция f() в классе A была переопределена классом B, поэтому функция в A() вызовет реализацию B(). Однако, если f() является частным методом и не может быть переопределен B, A.f() будет вызываться с более высокими приоритетами.
Но, как отмечали другие, это не очень хорошая практика.