Чи можна зробити обчислення перед super () у конструкторі?


81

Враховуючи те, що у мене є клас Base, який має конструктор одного аргументу з об’єктом TextBox як аргументом. Якщо у мене є клас Simple наступної форми:

public class Simple extends Base {
  public Simple(){
    TextBox t = new TextBox();
    super(t);
    //wouldn't it be nice if I could do things with t down here?
  }
}

Я отримаю повідомлення про помилку, яке повідомляє мені, що виклик super повинен бути першим викликом у конструкторі. Однак, як не дивно, я можу це зробити.

public class Simple extends Base {
  public Simple(){
    super(new TextBox());
  }
}

Чому це дозволено, а перший приклад - ні? Я розумію, що потрібно спочатку налаштувати підклас і, можливо, не дозволяти створювати екземпляри змінних об’єкта до виклику суперконструктора. Але t - це явно метод (локальна) змінна, то чому б це не дозволити?

Чи є спосіб обійти це обмеження? Чи є хороший і безпечний спосіб зберігати змінні для речей, які ви можете побудувати ДО виклику супер, але ПІСЛЯ введення конструктора? Або, загальніше, дозволяючи робити обчислення до того, як справді буде викликано super, але всередині конструктора?

Дякую.


3
з якої можливої ​​причини це позначено як gwt? Тому що ви пробували це у gwt ??

2
TextBox був класом GWT, але ні, я не маю значення.
Stephen Cagle

Відповіді:


89

Так, у вашому простому випадку є обхідний шлях. Ви можете створити приватний конструктор, який сприймає TextBoxяк аргумент, і викликати це із свого публічного конструктора.

public class Simple extends Base {
    private Simple(TextBox t) {
        super(t);
        // continue doing stuff with t here
    }

    public Simple() {
        this(new TextBox());
    }
}

Для більш складних речей потрібно використовувати фабричний або статичний фабричний метод.


1
Це, мабуть, є гарною відповіддю на запитання про те, як ви будете використовувати посилання на об’єкти, створені після побудови, але до супер.
Stephen Cagle

3
Це дуже зручний шаблон, і в значній мірі дозволяє повністю обійти вимогу, яка super()не з’являється після чогось іншого в конструкторі. У крайньому випадку ви навіть можете зв'язати кілька приватних конструкторів разом, хоча я не можу придумати жодної ситуації, коли мені це потрібно було б зробити, і якби я це зробив, я, мабуть, почав би задаватися питанням, чи є кращий спосіб досягти того, що робив.
Лоуренс Гонсалвес,

Так. Інші вже відповідали, чому це працює на Java, я просто хотів вказати на одне конкретне рішення. Я думаю, що для більшості інших обґрунтованих випадків, які ви можете придумати, також можна придумати прийнятний обхідний шлях. Зараз, щоб суперечити собі, є один випадок, коли це мене вкусило: я розширював клас, конструктор якого мав параметр Class. Я хотів зателефонувати super(this.getClass()), але оскільки вам не дозволено посилатися this, цей код був незаконним, хоча здається цілком розумним, що я мав би змогу знати фактичний клас на даний момент.
waxwing

@waxwing - це може здатися "цілком розумним", але це спричинить за собою розгляд getClass()методу як особливого випадку в JLS. Використовуйте <classname>.classзамість цього.
Stephen C

Використання <classname>.classв цьому випадку не було варіантом, оскільки я писав абстрактний клас, який люди могли б розширити, і мені потрібен був фактичний клас екземпляра.
waxwing

33

У мене була та ж проблема з обчисленнями до супердзвінка. Іноді перед дзвінком хочеться перевірити деякі умови super(). Наприклад, у вас є клас, який використовує багато ресурсів при створенні. підклас хоче отримати додаткові дані і, можливо, захоче перевірити їх спочатку перед викликом суперконструктора. Існує простий спосіб вирішити цю проблему. може виглядати трохи дивно, але це добре працює:

Використовуйте приватний статичний метод всередині вашого класу, який повертає аргумент суперконструктора і робить ваші перевірки всередині:

public class Simple extends Base {
  public Simple(){
    super(createTextBox());
  }

  private static TextBox createTextBox() {
    TextBox t = new TextBox();
    t.doSomething();
    // ... or more
    return t;
  }
}

8

Це вимагається мовою для того, щоб спочатку надійно побудувати суперклас . Зокрема, "Якщо конструктор явно не викликає конструктор суперкласу, компілятор Java автоматично вставляє виклик до конструктора без аргументу суперкласу."

У вашому прикладі суперклас може покладатися на стан tна момент будівництва. Ви завжди можете попросити копію пізніше.

Там є велике обговорення тут і тут .


2
Цікаво, що звучить так, що Java намагається запобігти одному з найважливіших випадків, коли обгортання коду навколо виклику конструктора суперкласу було б корисним: гарантування того, що якщо конструктор видає виняток, речі можуть очиститися. Наприклад, якщо один з конструкторів об'єкта перевантажує дані з переданого файлу, конструктор похідного класу може мати перевантаження, яке приймає ім'я файлу, передає конструктору суперкласу щойно відкритий файл, а потім закриває файл згодом. Якщо конструктор суперкласу кидає, як можна закрити файл?
supercat

@supercat: Ви повинні розробити спадщину або виключити її [Bloch, EffectiveJava , §17]; див. також це запитання та відповіді .
trashgod

Якщо для побудови об'єкта підкласу потрібен шаблон "придбати ресурс; побудувати об'єкт суперкласу; звільнити ресурс", як це реалізувати? Передати внутрішньому конструктору об’єкт, який реалізує метод з одним інтерфейсом, який буде використаний у випадку відмови? Надзвичайно працездатний, можливо, за рахунок ланцюжка деяких додаткових рівнів викликів конструктора.
supercat

Я мав розкіш виправити суперклас або використовувати композицію; пінг мене, якщо ви ставите це як запитання.
trashgod

1

Ви можете визначити статичний лямбда-постачальник, який може містити більш складну логіку.

public class MyClass {

    private static Supplier<MyType> myTypeSupplier = () -> {
        return new MyType();
    };

    public MyClass() {
        super(clientConfig, myTypeSupplier.get());
    }
}

0

Причина, через яку дозволено другий приклад, але не перший, швидше за все, тримати мову в порядку і не вводити дивні правила.

Дозвіл запуску будь-якого коду до виклику супер було б небезпечно, оскільки ви можете возитися з речами, які слід було ініціалізувати, але все ще не було. В основному, я думаю, ви можете зробити досить багато речей у виклику самого себе (наприклад, викликати статичний метод для обчислення деяких речей, які потрібно перейти до конструктора), але ви ніколи не зможете використовувати що-небудь з не -і все-таки повністю побудований об'єкт, що добре.


0

Ось як працює Java :-) Є технічні причини, чому її обрали саме таким чином. Дійсно може бути дивним, що ви не можете робити обчислення на місцевих жителях перед викликом супер, але в Java спочатку потрібно виділити об’єкт, і, отже, йому потрібно пройти весь шлях до Object, щоб усі поля були правильно ініціалізовані, перш ніж ви могли випадково модифікувати їх.

У вашому випадку є більша частина часу геттер, який дозволяє отримати доступ до параметра, який ви надали super (). Отже, ви б використали це:

супер (новий TextBox ());
остаточне поле TextBox = getWidget ();
... зроби свою справу ...

2
Те, що “всі поля правильно ініціалізовані” насправді не гарантується в Java (на відміну від IIUC, C ++), оскільки конструктор суперкласу може викликати нестандартний finalметод, який підклас замінює. Тоді перевизначення не побачить ініціалізованих полів з підкласу (навіть finalтих, що мають ініціалізатори екземплярів!). Деякі засоби статичного аналізу слушно попереджають про цю ситуацію.
Джессі Глік,

0

Це моє рішення, яке дозволяє створювати додатковий об'єкт, змінювати його без створення зайвих класів, полів, методів тощо.

class TextBox {
    
}

class Base {

    public Base(TextBox textBox) {
        
    }
}

public class Simple extends Base {

    public Simple() {
        super(((Supplier<TextBox>) () -> {
            var textBox = new TextBox();
            //some logic with text box
            return textBox;        
        }).get());
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.