Рубрики
Без рубрики

Невозможно Ссылаться На “X” До Вызова Конструктора Супертипа

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

Автор оригинала: baeldung.

1. Обзор

В этом коротком уроке мы покажем, как мы можем получить ошибку Не может ссылаться на “X” до вызова конструктора супертипа, и как ее избежать.

2. Цепочка конструкторов

Конструктор может вызывать только один другой конструктор. Этот вызов должен быть в первой строке его тела.

Мы можем вызвать конструктор того же класса с ключевым словом this , или мы можем вызвать конструктор суперкласса с ключевым словом super .

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

3. Наша Ошибка Компиляции

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

Давайте посмотрим, как мы можем столкнуться с этим.

3.1. Ссылка на метод экземпляра

В следующем примере мы увидим ошибку компиляции Не может ссылаться на “X” до вызова конструктора супертипа в строке 5. Обратите внимание, что конструктор пытается использовать метод экземпляра getErrorCode() слишком рано:

public class MyException extends RuntimeException {
    private int errorCode = 0;
    
    public MyException(String message) {
        super(message + getErrorCode()); // compilation error
    }

    public int getErrorCode() {
        return errorCode;
    }
}

Это ошибка, потому что u ntil super() завершен , нет экземпляра класса MyException . Поэтому мы пока не можем вызвать метод экземпляра getErrorCode() .

3.2. Ссылка на поле экземпляра

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

public class MyClass {

    private int myField1 = 10;
    private int myField2;

    public MyClass() {
        this(myField1); // compilation error
    }

    public MyClass(int i) {
        myField2 = i;
    }
}

Ссылка на поле экземпляра может быть сделана только после инициализации его класса, то есть после любого вызова this() или super() .

Итак, почему нет ошибки компилятора во втором конструкторе, который также использует поле экземпляра?

Помните , что все классы неявно производны от класса Object , и поэтому компилятор добавляет неявный вызов super() :

public MyClass(int i) {
    super(); // added by compiler
    myField2 = i;
}

Здесь конструктор Object вызывается до того, как мы получим доступ к myField2 , что означает, что мы в порядке.

4. Решения

Первое возможное решение этой проблемы тривиально: мы не вызываем второй конструктор. Мы явно делаем в первом конструкторе то, что хотели сделать во втором конструкторе.

В этом случае мы скопируем значение MyField1 в myField2 :

public class MyClass {

    private int myField1 = 10;
    private int myField2;

    public MyClass() {
        myField2 = myField1;
    }

    public MyClass(int i) {
        myField2 = i;
    }
}

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

Но, если мы вызываем второй конструктор по уважительной причине, например, чтобы избежать повторения кода, мы можем переместить код в метод:

public class MyClass {

    private int myField1 = 10;
    private int myField2;

    public MyClass() {
        setupMyFields(myField1);
    }

    public MyClass(int i) {
        setupMyFields(i);
    }

    private void setupMyFields(int i) {
        myField2 = i;
    }
}

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

Третьим решением может быть использование статических полей или методов . Если мы изменим MyField1 на статическую константу, то компилятор тоже будет доволен:

public class MyClass {

    private static final int SOME_CONSTANT = 10;
    private int myField2;

    public MyClass() {
        this(SOME_CONSTANT);
    }

    public MyClass(int i) {
        myField2 = i;
    }
}

Мы должны отметить, что создание поля static означает, что оно становится общим для всех экземпляров этого объекта, поэтому это не слишком легкое изменение.

Чтобы static был правильным ответом, нам нужна веская причина. Например, возможно, значение на самом деле не поле, а константа, поэтому имеет смысл сделать его статическим и окончательным . Возможно, метод построения, который мы хотели вызвать, не нуждается в доступе к членам экземпляра класса, что означает, что он должен быть статическим .

5. Заключение

В этой статье мы видели, как ссылка на члены экземпляра перед вызовом super() или this() приводит к ошибке компиляции. Мы видели, как это произошло с явно объявленным базовым классом, а также с неявным базовым классом Object .

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

Как всегда, исходный код для этого примера можно найти на GitHub .