Можно ли выполнить вычисление до 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, но ПОСЛЕ того, как вы вошли в конструктор? Или, в более общем смысле, разрешить выполнение вычислений до фактического вызова super, но в конструкторе?

Спасибо.

Стивен Кейгл
источник
3
по какой возможной причине это было помечено как gwt? Потому что вы пробовали это в gwt ??
2
TextBox был классом GWT, но нет, я полагаю, это не имеет значения.
Стивен Кейгл,

Ответы:

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
Казалось бы, это хороший ответ на вопрос о том, как вы будете использовать ссылки на объекты, созданные после построения, но до super.
Стивен Кейгл,
3
Это очень удобный шаблон, который в значительной степени позволяет полностью обойти требование, которое super()не появляется после чего-либо еще в конструкторе. В крайних случаях вы даже можете связать несколько частных конструкторов вместе, хотя я не могу представить себе ситуацию, в которой мне нужно было бы это сделать, и если бы я это сделал, я бы, вероятно, начал задаваться вопросом, есть ли лучший способ выполнить то, что я делал.
Лоуренс Гонсалвес,
Ага. Другие уже ответили, почему это работает именно так на Java, я просто хотел указать на один конкретный обходной путь. Я думаю , что для большинства других уважительных случаев вы можете придумать, это также можно придумать приемлемые обходной. Теперь, чтобы противоречить себе, есть один случай, когда это укусило меня: я расширял класс, конструктор которого имел параметр Class. Я хотел позвонить super(this.getClass()), но, поскольку вам не разрешено ссылаться this, этот код был незаконным, хотя кажется вполне разумным, что я должен знать реальный класс на этом этапе.
свиристель
@waxwing - это может показаться «вполне разумным», но это повлечет за собой рассмотрение getClass()метода как особого случая в JLS. <classname>.classВместо этого используйте .
Stephen C
В <classname>.classэтом случае использование не было вариантом, потому что я писал абстрактный класс, который люди могли расширять, и мне нужен был фактический класс экземпляра.
свиристель
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());
    }
}
Lowyjk
источник
0

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

Разрешить запуск любого кода до вызова super было бы опасно, так как вы можете испортить вещи, которые должны были быть инициализированы, но еще не были. В принципе, я предполагаю, что вы можете делать довольно много вещей при вызове самого super (например, вызывать статический метод для вычисления некоторых вещей, которые должны перейти в конструктор), но вы никогда не сможете использовать что-либо из не -пока-полностью построенный объект, что хорошо.

КодированиеБессонница
источник
0

Так работает Java :-) Есть технические причины, по которым она была выбрана именно так. Это действительно может быть странно, что вы не можете выполнять вычисления на локальных жителях перед вызовом super, но в Java объект должен быть сначала выделен, и, следовательно, он должен пройти весь путь до объекта, чтобы все поля были правильно инициализированы, прежде чем вы сможете случайно изменить их.

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

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());
    }
}
Павел_К
источник