Полиморфный метод в конструкторе (Java)

Класс 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

Ответ 1

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

Ответ 2

Всякий раз, когда вы создаете экземпляр подкласса, сначала запускается конструктор суперклассов (неявный 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 и т.д.) предупреждают вас, если вы попытаетесь это сделать.

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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

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

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