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

Java требует, чтобы, если вы вызываете this() или 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 говорит, что "вызов super должен быть первым выражением в конструкторе". Компилятор Eclipse говорит, что "вызов конструктора должен быть первым оператором в конструкторе".

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

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

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

Ответ 1

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

То, что вы пытаетесь сделать, передать аргументы супер-конструктору, совершенно законно, вам просто нужно построить эти аргументы, как вы делаете, или передать их вашему конструктору, а затем передать их в 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);
        }
}

В тех случаях, когда класс parent имеет конструктор по умолчанию, вызов super автоматически устанавливается с помощью compiler. Так как каждый класс в Java наследует от Object, конструктор объектов должен быть вызван как-то, и он должен быть выполнен первым. Автоматическая вставка супер() компилятором позволяет это. Принудительное включение супер, чтобы гарантировать, что тела конструктора выполняются в правильном порядке: 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;
  }
}

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

Ответ 4

Я уверен (знакомы с вызовом Java Specification chime in), чтобы он не позволял вам (а) иметь возможность использовать частично построенный объект и (b), заставляя конструктор родительского класса строить на "свежий" объект.

Некоторые примеры "плохой" вещи были бы следующими:

class Thing
{
    final int x;
    Thing(int x) { this.x = x; }
}

class Bad1 extends Thing
{
    final int z;
    Bad1(int x, int y)
    {
        this.z = this.x + this.y; // WHOOPS! x hasn't been set yet
        super(x);
    }        
}

class Bad2 extends Thing
{
    final int y;
    Bad2(int x, int y)
    {
        this.x = 33;
        this.y = y; 
        super(x); // WHOOPS! x is supposed to be final
    }        
}

Ответ 5

Вы спросили, почему, а другие ответы, imo, на самом деле не говорят, почему это нормально, чтобы назвать ваш супер конструктор, но только если это самая первая строка. Причина в том, что вы на самом деле не вызываете конструктор. В С++ эквивалентный синтаксис

MySubClass: MyClass {

public:

 MySubClass(int a, int b): MyClass(a+b)
 {
 }

};

Когда вы видите предложение инициализатора как таковое, перед открытой скобкой вы знаете его особенным. Он запускается до запуска любого из остальных конструкторов и фактически до инициализации любой из переменных-членов. Это не так уж и отличается для Java. Там можно запустить некоторый код (другие конструкторы), прежде чем конструктор начнет работу, прежде чем инициализируются какие-либо элементы подкласса. И этот способ заключается в том, чтобы поместить "вызов" (например, super) в первую строку. (В некотором роде, что super или this имеет вид перед первой открытой фигурной скобкой, даже если вы вводите ее после нее, потому что она будет выполнена до того, как вы дойдете до точки, что все будет полностью построено.) Любой другой код после открытой скобки (например, int c = a + b;) компилятор говорит "о, хорошо, никаких других конструкторов, мы можем все инициализировать". Таким образом, он убегает и инициализирует ваш суперкласс и ваших членов и еще много чего, а затем начинает выполнение кода после открытой фигурной скобки.

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

Ответ 6

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

ConstructorBody:       {ExplicitConstructorInvocation opt   BlockStatements opt}

Первый оператор тела конструктора может быть:
- явный вызов другого конструктора того же класса (с использованием ключевого слова "this") ИЛИ
- прямого суперкласса (с использованием ключевого слова "супер" )

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

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

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

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

Ответ 7

Я полностью согласен, ограничения слишком сильны. Использование статического вспомогательного метода (как предполагал Том Ховин - подсказка) или перетаскивание всех "предварительных суперпоследовательных вычислений" в одно выражение в параметре не всегда возможно, например:

class Sup {
    public Sup(final int x_) { 
        //cheap constructor 
    }
    public Sup(final Sup sup_) { 
        //expensive copy constructor 
    }
}

class Sub extends Sup {
    private int x;
    public Sub(final Sub aSub) {
        /* for aSub with aSub.x == 0, 
         * the expensive copy constructor is unnecessary:
         */

         /* if (aSub.x == 0) { 
          *    super(0);
          * } else {
          *    super(aSub);
          * } 
          * above gives error since if-construct before super() is not allowed.
          */

        /* super((aSub.x == 0) ? 0 : aSub); 
         * above gives error since the ?-operator type is Object
         */

        super(aSub); // much slower :(  

        // further initialization of aSub
    }
}

Используя исключение "объект, еще не построенный", как предложил Карсон Майерс, поможет, но проверка этого при каждой конструкции объекта замедлит выполнение. Я бы предпочел компилятор Java, который делает лучшее дифференцирование (вместо того, чтобы запретить if-statement, но разрешая? -оператор внутри параметра), даже если это усложняет спецификацию языка.

Ответ 8

Таким образом, это не останавливает вас от выполнения логики до вызова супер. Это просто останавливает вас от выполнения логики, которую вы не можете в одно выражение.

На самом деле вы можете выполнить логику с несколькими искушениями, вам просто нужно обернуть свой код в статической функции и вызвать его в супер-заявлении.

Используя ваш пример:

public class MySubClassC extends MyClass {
    public MySubClassC(Object item) {
        // Create a list that contains the item, and pass the list to super
        super(createList(item));  // OK
    }

    private static List createList(item) {
        List list = new ArrayList();
        list.add(item);
        return list;
    }
}

Ответ 9

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

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}

class Parent {
    public Parent() {
        System.out.println("In parent");
    }
}

class Child extends Parent {

    {
        System.out.println("In initializer");
    }

    public Child() {
        super();
        System.out.println("In child");
    }
}

Это выведет:

В родительском В инициализаторе
У ребенка

Ответ 10

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

Если вы разрешаете вызову super() или this() перемещаться, есть еще несколько вариантов проверки. Например, если вы переместите вызов super() или this() в условный if(), возможно, он должен быть достаточно умным, чтобы вставить неявное super() в else. Возможно, вам понадобится знать, как сообщать об ошибке, если вы вызываете super() дважды, или используйте super() и this() вместе. Возможно, потребуется отключить вызовы методов на приемнике до тех пор, пока не вызовет вызов super() или this() и не выяснит, когда это становится сложным.

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

Ответ 11

Я нашел волнение.

Это не скомпилируется:

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

Это работает:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        this(a + b);
        doSomething2(a);
        doSomething3(b);
    }

    private MySubClass(int c) {
        super(c);
        doSomething(c);
    }
}

Ответ 12

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

Простая демонстрация:

class A {
    A() {
        System.out.println("Inside A constructor.");
    }
}

class B extends A {
    B() {
        System.out.println("Inside B constructor.");
    }
}

class C extends B {
    C() {
        System.out.println("Inside C constructor.");
    }
}

class CallingCons {
    public static void main(String args[]) {
        C c = new C();
    }
}

Выход из этой программы:

Inside A constructor
Inside B constructor
Inside C constructor

Ответ 13

TL;DR:

Другие ответы занялись вопросом "почему". Я поставлю хак вокруг этого ограничения:

Основная идея - захватить оператор super с помощью встроенных инструкций. Это можно сделать, замаскировав свои выражения как выражения.

TSDR:

Рассмотрим, что мы хотим сделать Statement1() до Statement9(), прежде чем называть super():

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        super(_1, _2, _3); // compiler rejects because this is not the first line
    }
}

Компилятор, конечно, отклонит наш код. Поэтому мы можем это сделать:

// This compiles fine:

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        super(F(_1), _2, _3);
    }

    public static T1 F(T1 _1) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        return _1;
    }
}

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

Вот более подробный пример:

public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        i = i * 10 - 123;
        if (s.length() > i) {
            s = "This is substr s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        Object obj = Static_Class.A_Static_Method(i, s, t1);
        super(obj, i, "some argument", s, t1, t2); // compiler rejects because this is not the first line
    }
}

Переработано в:

// This compiles fine:

public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        super(Arg1(i, s, t1), Arg2(i), "some argument", Arg4(i, s), t1, Arg6(i, t1));
    }

    private static Object Arg1(int i, String s, T1 t1) {
        i = Arg2(i);
        s = Arg4(s);
        return Static_Class.A_Static_Method(i, s, t1);
    }

    private static int Arg2(int i) {
        i = i * 10 - 123;
        return i;
    }

    private static String Arg4(int i, String s) {
        i = Arg2(i);
        if (s.length() > i) {
            s = "This is sub s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        return s;
    }

    private static T2 Arg6(int i, T1 t1) {
        i = Arg2(i);
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        return t2;
    }
}

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

Ответ 14

Я знаю, что немного опоздал на вечеринку, но я использовал этот трюк пару раз (и я знаю это немного необычно):

Я создаю общий интерфейс InfoRunnable<T> одним способом:

public T run(Object... args);

И если мне нужно что-то сделать, прежде чем передать его конструктору, я просто сделаю следующее:

super(new InfoRunnable<ThingToPass>() {
    public ThingToPass run(Object... args) {
        /* do your things here */
    }
}.run(/* args here */));

Ответ 15

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

public MyClass {
        public MyClass(String someArg) {
                System.out.println(someArg);
        }
}

он обращается к следующему (расширение и супер просто скрыты):

public MyClass extends Object{
        public MyClass(String someArg) {
                super();
                System.out.println(someArg);
        }
}

Сначала мы создаем Object, а затем расширяем этот объект до MyClass. Мы не можем создать MyClass до Object. Простое правило состоит в том, что родительский конструктор должен быть вызван до дочернего конструктора. Но мы знаем, что классы могут иметь больше одного конструктора. Java позволяет нам выбрать конструктор, который будет вызываться (либо он будет super(), либо super(yourArgs...)). Итак, когда вы пишете super(yourArgs...), вы переопределяете конструктор, который будет вызываться для создания родительского объекта. Вы не можете выполнять другие методы до super(), потому что объект еще не существует (но после super() будет создан объект, и вы сможете делать все, что хотите).

Так почему же мы не можем выполнить this() после любого метода? Как вы знаете, this() является конструктором текущего класса. Также у нас может быть разное количество конструкторов в нашем классе и называть их как this() или this(yourArgs...). Как я уже сказал, каждый конструктор имеет скрытый метод super(). Когда мы пишем наш пользовательский super(yourArgs...), удалим super() с помощью super(yourArgs...). Также, когда мы определяем this() или this(yourArgs...), мы также удаляем наш super() в текущем конструкторе, потому что если super() были с this() в том же методе, он создавал бы более одного родительского объекта. Вот почему для метода this() применяются те же правила. Он просто ретранслирует создание родительского объекта в другой дочерний конструктор и этот конструктор вызывает конструктор super() для создания родителя. Итак, код будет таким:

public MyClass extends Object{
        public MyClass(int a) {
                super();
                System.out.println(a);
        }
        public MyClass(int a, int b) {
                this(a);
                System.out.println(b);
        }
}

Как говорят другие, вы можете выполнить такой код:

this(a+b);

также вы можете выполнить код следующим образом:

public MyClass(int a, SomeObject someObject) {
    this(someObject.add(a+5));
}

Но вы не можете выполнить такой код, потому что ваш метод еще не существует:

public MyClass extends Object{
    public MyClass(int a) {

    }
    public MyClass(int a, int b) {
        this(add(a, b));
    }
    public int add(int a, int b){
        return a+b;
    }
}

Также вы должны иметь конструктор super() в цепочке методов this(). Вы не можете создать объект таким образом:

public MyClass{
        public MyClass(int a) {
                this(a, 5);
        }
        public MyClass(int a, int b) {
                this(a);
        }
}

Ответ 16

class C
{
    int y,z;

    C()
    {
        y=10;
    }

    C(int x)
    {
        C();
        z=x+y;
        System.out.println(z);
    }
}

class A
{
    public static void main(String a[])
    {
        new C(10);
    }
}

См. пример, если мы вызываем конструктор C(int x), тогда значение z зависит от y, если мы не вызываем C() в первой строке, тогда это будет проблема для z. z не сможет получить правильное значение.