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




textfield java методы (16)

Я достаточно уверен (знакомы с перечнем спецификаций 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
    }        
}

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

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


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

ConstructorBody: {ExplicitConstructorInvocation opt BlockStatements opt }

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

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

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

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

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


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


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

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, но разрешая? -Оператор внутри параметра), даже если это усложняет спецификацию языка.


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


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

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

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


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

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


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

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

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

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


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

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

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

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

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


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

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

public T run(Object... args);

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

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

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

class Good {
    int essential1;
    int essential2;

    Good(int n) {
        if (n > 100)
            throw new IllegalArgumentException("n is too large!");
        essential1 = 1 / n;
        essential2 = n + 2;
    }
}

class Bad extends Good {
    Bad(int n) {
        try {
            super(n);
        } catch (Exception e) {
            // Exception is ignored
        }
    }

    public static void main(String[] args) {
        Bad b = new Bad(0);
//        b = new Bad(101);
        System.out.println(b.essential1 + b.essential2);
    }
}

Исключение при построении почти всегда указывает на то, что создаваемый объект не может быть правильно инициализирован, теперь находится в плохом состоянии, непригодным для использования и должен быть собран мусором. Однако конструктор подкласса имеет возможность игнорировать исключение, возникшее в одном из своих суперклассов, и возвращать частично инициализированный объект. В приведенном выше примере, если приведенный аргумент new Bad()равен либо 0, либо больше 100, то он не инициализируется essential1и essential2не инициализируется.

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

class Bad extends Good {
    Bad(int n) {
        for (int i = 0; i < n; i++)
            super(i);
    }
}

Смешно, не так ли? Сколько объектов мы создаем в этом примере? Один? Два? Или, может быть, ничего ...

Разрешение вызова super()или this()в середине конструктора откроет ящик Пандоры отвратительных конструкторов.

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

В таких случаях у вас есть следующие возможности:

  1. Используйте шаблон, представленный в этом ответе , что позволяет обойти это ограничение.
  2. Подождите, пока команда Java разрешит предварительный super()и предварительный this()код. Это может быть сделано путем введения ограничения на то, где super()или this()может произойти в конструкторе. Фактически, даже сегодняшний компилятор способен отличать хорошие и плохие (или потенциально плохие) случаи со степенью достаточно, чтобы надежно разрешить статическое добавление кода в начале конструктора. Действительно, предположим, что super()и this()возвращаем thisссылку и, в свою очередь, ваш конструктор имеет

return this;

в конце. Как и компилятор отклоняет код

public int get() {
    int x;
    for (int i = 0; i < 10; i++)
        x = i;
    return x;
}

public int get(int y) {
    int x;
    if (y > 0)
        x = y;
    return x;
}

public int get(boolean b) {
    int x;
    try {
        x = 1;
    } catch (Exception e) {
    }
    return x;
}

с ошибкой «переменная x может не быть инициализирована», она может делать это на thisпеременной, делая ее проверки на ней, как и на любой другой локальной переменной. Единственное различие не thisможет быть назначено никакими средствами, кроме super()или this()вызова (и, как обычно, если такой вызов у ​​конструктора отсутствует, super()он неявно вставлен компилятором в начале) и может не назначаться дважды. В случае каких-либо сомнений (например, в первом get(), где xвсегда всегда назначается), компилятор может вернуть ошибку. Это было бы лучше, чем просто вернуть ошибку на любом конструкторе, где есть что-то, кроме комментария до super()или this().


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

MySubClass: MyClass {

public:

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

};

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

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







constructor