учебник - textfield java методы




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

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
    }
}

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

Существуют аналогичные правила для вызова this() . Компилятор говорит, что «вызов этого должен быть первым выражением в конструкторе».

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


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

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

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

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

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

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

Результатом этой программы является:

Inside A's constructor
Inside B's constructor
Inside C's constructor

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

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

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

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;
    }
}

Tldr:

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

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

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;
    }
}

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


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

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");
    }
}

Это приведет к выводу:

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


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

То, что вы пытаетесь сделать, передать аргументы супер-конструктору, совершенно законно, вам просто нужно построить эти аргументы inline, как вы это делаете, или передать их вашему конструктору, а затем передать их 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 класс имеет конструктор по умолчанию, вызов супер автоматически устанавливается compiler . Поскольку каждый класс в Java наследуется от Object , конструктор объектов должен быть вызван как-то, и он должен быть выполнен первым. Автоматическая вставка супер () компилятором позволяет это. Принудительное включение супер, чтобы убедиться, что тела конструктора выполняются в правильном порядке: Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth


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

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

Исторически это () или super () должно быть первым в конструкторе. Это ограничение никогда не было популярным и воспринималось как произвольное. Существовал ряд тонких причин, в том числе проверка invokespecial, которые способствовали этому ограничению. На протяжении многих лет мы рассматривали их на уровне VM, до того момента, когда становится целесообразным рассмотреть возможность отмены этого ограничения не только для записей, но и для всех конструкторов.


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

ConstructorBody: {ExplicitConstructorInvocation opt BlockStatements opt }

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

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

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

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

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


Фактически, super() - это первый оператор конструктора, потому что он должен полностью сформировать свой суперкласс перед построением подкласса. Даже если у вас нет super() в вашем первом выражении, компилятор добавит его для вас!


Я достаточно уверен (знакомы с перечнем спецификаций Java), чтобы он не позволял вам (а) разрешать использовать частично построенный объект и (б), заставляя конструктор родительского класса строить на «свежем» "объект.

Некоторыми примерами «плохой» вещи были бы:

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
    }        
}

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

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

public T run(Object... args);

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

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

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

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;
  }
}

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


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

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's type is Object
         */

        super(aSub); // much slower :(  

        // further initialization of aSub
    }
}

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


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 не сможет получить правильное значение.





constructor