Перейти к основному содержимому

Невозможно ссылаться на «X» до вызова конструктора супертипа

· 4 мин. чтения

1. Обзор

В этом кратком руководстве мы покажем, как мы можем получить ошибку Cannot reference «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;
}
}

Это ошибка, потому что до тех пор , пока 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;
}
}

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

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

5. Вывод

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

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

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