Инициализировать поле перед запуском суперконструктора?
В Java есть ли способ инициализировать поле до запуска суперструктора?
Даже самые уродливые хаки, которые я могу придумать, отвергаются компилятором:
class Base
{
Base(String someParameter)
{
System.out.println(this);
}
}
class Derived extends Base
{
private final int a;
Derived(String someParameter)
{
super(hack(someParameter, a = getValueFromDataBase()));
}
private static String hack(String returnValue, int ignored)
{
return returnValue;
}
public String toString()
{
return "a has value " + a;
}
}
Примечание. Проблема исчезла, когда я переключился с наследования на делегирование, но мне все равно хотелось бы знать.
Ответы
Ответ 1
Нет, нет никакого способа сделать это.
В соответствии с спецификациями языка переменные экземпляра даже не инициализируются до тех пор, пока не будет выполнен вызов super()
.
Это шаги, выполняемые во время шага конструктора создания экземпляра класса, взятого из ссылки:
- Назначьте аргументы для конструктора вновь созданному параметру переменные для этого вызова конструктора.
- Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора того же класса (используя это), затем оцените аргументы и обработайте вызов конструктора рекурсивно используя эти пять шагов. Если этот конструктор вызов завершается внезапно, то эта процедура завершается внезапно по той же причине; в противном случае, перейдите к шагу 5.
- Этот конструктор не начинается с явного конструктора вызов другого конструктора в том же классе (с использованием этого). Если этот конструктор предназначен для класса, отличного от Object, тогда это конструктор начнется с явного или неявного вызова конструктор суперкласса (с использованием супер). Оцените аргументы и обрабатывать вызов конструктора суперкласса рекурсивно, используя эти пять шагов. Если этот вызов конструктора завершен внезапно, эта процедура завершается внезапно для того же причина. В противном случае перейдите к шагу 4.
- Выполнить инициализаторы экземпляра и инициализаторы переменных экземпляра для этого класса, присваивая значения переменной экземпляра инициализаторы к соответствующим переменным экземпляра, в слева направо, в котором они отображаются в текстовом виде в источнике код для класса. Если выполнение любого из этих инициализаторов приводит к исключению, тогда никакие инициализаторы не обрабатываются и эта процедура завершается внезапно с тем же исключением. В противном случае перейдите к шагу 5.
- Выполните оставшуюся часть тела этого конструктора. Если это выполнение завершается резко, то эта процедура завершается внезапно для та же самая причина. В противном случае эта процедура выполняется нормально.
Ответ 2
Супер конструктор будет работать в любом случае, но поскольку мы говорим о "уродливых хаках", мы можем воспользоваться этим
public class Base {
public Base() {
init();
}
public Base(String s) {
}
public void init() {
//this is the ugly part that will be overriden
}
}
class Derived extends Base{
@Override
public void init(){
a = getValueFromDataBase();
}
}
Я никогда не предлагаю использовать такие хаки.
Ответ 3
Как говорили другие, вы не можете инициализировать поле экземпляра перед вызовом конструктора суперкласса.
Но есть обходные пути. Один из них заключается в создании класса factory, который получает значение и передает его в конструктор класса Derived.
class DerivedFactory {
Derived makeDerived( String someParameter ) {
int a = getValueFromDataBase();
return new Derived( someParameter, a );
}
}
class Derived extends Base
{
private final int a;
Derived(String someParameter, int a0 ) {
super(hack(someParameter, a0));
a = a0;
}
...
}
Ответ 4
У меня есть способ сделать это.
class Derived extends Base
{
private final int a;
// make this method private
private Derived(String someParameter,
int tmpVar /*add an addtional parameter*/) {
// use it as a temprorary variable
super(hack(someParameter, tmpVar = getValueFromDataBase()));
// assign it to field a
a = tmpVar;
}
// show user a clean constructor
Derived(String someParameter)
{
this(someParameter, 0)
}
...
}
Ответ 5
Он запрещен спецификацией языка Java (раздел 8.8.7):
Первый оператор тела конструктора может быть явным вызов другого конструктора того же класса или прямого суперкласс.
Тело конструктора должно выглядеть следующим образом:
ConstructorBody:
{ ExplicitConstructorInvocationopt BlockStatementsopt }