是否可以在构造函数中的 super()之前进行计算?

假设我有一个类 Base,它有一个参数构造函数,其参数是一个 TextBox 对象。如果我有一个类,简单的形式如下:

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 之前完成计算,但是在构造函数中?

谢谢你。

40392 次浏览

The reason why the second example is allowed but not the first is most likely to keep the language tidy and not introduce strange rules.

Allowing any code to run before super has been called would be dangerous since you might mess with things that should have been initialized but still haven't been. Basically, I guess you can do quite a lot of things in the call to super itself (e.g. call a static method for calculating some stuff that needs to go to the constructor), but you'll never be able to use anything from the not-yet-completely-constructed object which is a good thing.

It is required by the language in order to ensure that the superclass is reliably constructed first. In particular, "If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass."

In your example, the superclass may rely on the state of t at construction time. You can always ask for a copy later.

There's an extensive discussion here and here.

That's how Java works :-) There are technical reasons why it was chosen this way. It might indeed be odd that you can not do computations on locals before calling super, but in Java the object must first be allocated and thus it needs to go all the way up to Object so that all fields are correctly initialized before you can modify them.

In your case there is most of the time a getter that allows you to access the parameter you gave to super(). So you would use this:

super( new TextBox() );
final TextBox box = getWidget();
... do your thing...

Yes, there is a workaround for your simple case. You can create a private constructor that takes TextBox as an argument and call that from your public constructor.

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


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

For more complicated stuff, you need to use a factory or a static factory method.

I had the same problem with computation before super call. Sometimes you want to check some conditions before calling super(). For example, you have a class that uses a lot of resources when created. the sub-class wants some extra data and might want to check them first, before calling the super-constructor. There is a simple way around this problem. might look a bit strange, but it works well:

Use a private static method inside your class that returns the argument of the super-constructor and make your checks inside:

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


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

You can define a static supplier lambda which can contain more complicated logic.

public class MyClass {


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


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

This is my solution that allows to create additional object, modify it without creating extra classes, fields, methods etc.

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