Вопрос-ответ

Why do this() and super() have to be the first statement in a constructor?

Почему this() и super() должны быть первыми операторами в конструкторе?

Java требует, чтобы при вызове this() or super() в конструкторе это была первая инструкция. Почему?

Например:

public class MyClass {
public MyClass(int x) {}
}

public class MySubClass extends MyClass {
public MySubClass(int a, int b) {
int c = a + b;
super(c); // COMPILE ERROR
}
}

Компилятор Sun говорит, call to super must be first statement in constructor. Компилятор Eclipse говорит, Constructor call must be the first statement in a constructor.

Однако вы можете обойти это, немного изменив порядок кода:

public class MySubClass extends MyClass {
public MySubClass(int a, int b) {
super(a + b); // OK
}
}

Вот еще один пример:

public class MyClass {
public MyClass(List list) {}
}

public class MySubClassA extends MyClass {
public MySubClassA(Object item) {
// Create a list that contains the item, and pass the list to super
List list = new ArrayList();
list.add(item);
super(list); // COMPILE ERROR
}
}

public class MySubClassB extends MyClass {
public MySubClassB(Object item) {
// Create a list that contains the item, and pass the list to super
super(Arrays.asList(new Object[] { item })); // OK
}
}

Итак, это не мешает вам выполнять логику перед вызовом super(). Это просто мешает вам выполнять логику, которую вы не можете вместить в одно выражение.

Существуют аналогичные правила для вызова this(). Компилятор говорит, call to this must be first statement in constructor.

Почему компилятор имеет эти ограничения? Можете ли вы привести пример кода, в котором, если бы у компилятора не было этого ограничения, произошло бы что-то плохое?

Переведено автоматически
Ответ 1

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

То, что вы пытаетесь сделать, передать аргументы в суперконструктор, совершенно законно, вам просто нужно построить эти аргументы встроенными, как вы делаете, или передать их в свой конструктор, а затем передать их в super:

public MySubClassB extends MyClass {
public MySubClassB(Object[] myArray) {
super(myArray);
}
}

Если компилятор не применял это, вы могли бы сделать это:

public MySubClassB extends MyClass {
public MySubClassB(Object[] myArray) {
someMethodOnSuper(); //ERROR super not yet constructed
super(myArray);
}
}

В случаях, когда родительский класс имеет конструктор по умолчанию, вызов super автоматически вставляется компилятором. Поскольку каждый класс в Java наследуется от Object, конструктор объектов должен быть каким-то образом вызван и выполняться первым. Автоматическая вставка super() компилятором позволяет это. Принудительное отображение super первым обеспечивает выполнение тел конструктора в правильном порядке, который должен быть следующим: Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

Ответ 2

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

public class Foo extends Baz {
private final Bar myBar;

public Foo(String arg1, String arg2) {
// ...
// ... Some other stuff needed to construct a 'Bar'...
// ...
final Bar b = new Bar(arg1, arg2);
super(b.baz()):
myBar = b;
}
}

Таким образом, в основном создается объект на основе параметров конструктора, сохраняется объект в члене, а также передается результат метода для этого объекта в конструктор super . Создание элемента final также было разумно важно, поскольку природа класса такова, что он неизменяем. Обратите внимание, что при построении Bar фактически требуется несколько промежуточных объектов, поэтому в моем реальном варианте использования это невозможно свести к однострочному.

В итоге я заставил его работать примерно так:

public class Foo extends Baz {
private final Bar myBar;

private static Bar makeBar(String arg1, String arg2) {
// My more complicated setup routine to actually make 'Bar' goes here...
return new Bar(arg1, arg2);
}

public Foo(String arg1, String arg2) {
this(makeBar(arg1, arg2));
}

private Foo(Bar bar) {
super(bar.baz());
myBar = bar;
}
}

Легальный код, и он выполняет задачу выполнения нескольких инструкций перед вызовом суперконструктора.

Ответ 3

Потому что так сказано в JLS. Можно ли совместимым образом изменить JLS, чтобы разрешить это? Да.

Однако это усложнило бы спецификацию языка, которая и так более чем достаточно сложна. Это было бы не очень полезно, и есть способы обойти это (вызвать другой конструктор с результатом статического метода или лямбда-выражения this(fn()) - метод вызывается перед другим конструктором, а следовательно, и перед суперконструктором). Таким образом, соотношение мощности к весу при выполнении изменения неблагоприятно.

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

Рассмотрим эти недопустимые примеры.

super(this.x = 5);

super(this.fn());

super(fn());

super(x);

super(this instanceof SubClass);
// this.getClass() would be /really/ useful sometimes.

This example is legal, but "wrong".

class MyBase {
MyBase() {
fn();
}
abstract void fn();
}
class MyDerived extends MyBase {
void fn() {
// ???
}
}

In the above example, if MyDerived.fn required arguments from the MyDerived constructor they would need to be sleazed through with a ThreadLocal. ;(

Incidentally, since Java 1.4, the synthetic field that contains the outer this is assigned before inner classes super constructor is called. This caused peculiar NullPointerException events in code compiled to target earlier versions.

Note also, in the presence of unsafe publication, construction can be viewed reordered by other threads, unless precautions are made.

Редактировать март 2018: В сообщениях Записи: построение и проверка Oracle предлагает снять это ограничение (но в отличие от C #, this будет определенно неназначен (DU) перед объединением конструктора в цепочку).


Исторически this() или super() должны быть первыми в конструкторе. Это ограничение никогда не было популярным и воспринималось как произвольное. Был ряд тонких причин, включая проверку invokespecial , которые способствовали этому ограничению. За эти годы мы разобрались с ними на уровне виртуальной машины до такой степени, что стало практичным рассмотреть возможность снятия этого ограничения не только для записей, но и для всех конструкторов.


Ответ 4

Просто потому, что такова философия наследования. И согласно спецификации языка Java, именно так определяется тело конструктора:

ConstructorBody: { ExplicitConstructorInvocationopt BlockStatementsopt }

Первый оператор тела конструктора может быть любым


  • явный вызов другого конструктора того же класса (с использованием ключевого слова "this"); или

  • явный вызов прямого суперкласса (с использованием ключевого слова "super")

Если тело конструктора не начинается с явного вызова конструктора и объявляемый конструктор не является частью объекта исходного класса, то тело конструктора неявно начинается с вызова конструктора суперкласса "super();", вызова конструктора его прямого суперкласса, который не принимает аргументов. И так далее .. будет вызываться целая цепочка конструкторов вплоть до конструктора Object; "Все классы в платформе Java являются потомками Object". Эта штука называется "Связывание конструктора в цепочку".

Итак, почему это?
И причина, по которой Java определила ConstructorBody таким образом, заключается в том, что им нужно было поддерживать иерархию объекта. Помните определение наследования; Это расширение класса. С учетом сказанного, вы не можете расширить то, чего не существует. Сначала необходимо создать базовый (суперкласс), затем вы можете получить его (подкласс). Вот почему они назвали их родительским и дочерним классами; у вас не может быть дочернего класса без родительского.

На техническом уровне подкласс наследует все элементы (поля, методы, вложенные классы) от своего родителя. И поскольку конструкторы НЕ являются членами (они не принадлежат объектам. Они отвечают за создание объектов), поэтому они НЕ наследуются подклассами, но их можно вызывать. И поскольку во время создания объекта выполняется только ОДИН конструктор. Итак, как мы можем гарантировать создание суперкласса при создании объекта подкласса? Таким образом, концепция "цепочки конструкторов"; таким образом, у нас есть возможность вызывать другие конструкторы (например, super) из текущего конструктора. И Java требовали, чтобы этот вызов был ПЕРВОЙ строкой в конструкторе подкласса, чтобы поддерживать иерархию и гарантировать ее. Они предполагают, что если вы сначала явно не создадите родительский объект (например, если вы забыли о нем), они сделают это неявно за вас.

Эта проверка выполняется во время компиляции. Но я не уверен, что произойдет во время выполнения, какую ошибку во время выполнения мы получим, ЕСЛИ Java не выдает ошибку компиляции, когда мы явно пытаемся выполнить базовый конструктор из конструктора подкласса в середине его тела, а не из самой первой строки ...

2023-12-09 17:28 java