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

b4qexyjb  于 2023-04-19  发布在  Java
关注(0)|答案(8)|浏览(238)

假设我有一个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实际被调用之前,但在构造函数中进行计算?

w6mmgewl

w6mmgewl1#

是的,对于简单的情况有一个解决方案。您可以创建一个以TextBox为参数的私有构造函数,然后从公共构造函数调用它。

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

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

对于更复杂的东西,您需要使用工厂或静态工厂方法。

m528fe3b

m528fe3b2#

我在super调用之前遇到了同样的问题。有时候你想在调用super()之前检查一些条件。例如,你有一个类,在创建时使用了很多资源。子类想要一些额外的数据,可能想在调用超级构造函数之前先检查它们。有一个简单的方法可以解决这个问题。可能看起来有点奇怪,但它工作得很好:
在类中使用私有静态方法,该方法返回超级构造函数的参数,并在内部进行检查:

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

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

ruyhziif3#

它是required,以确保超类被可靠地构造 * 第一 *。特别是,“如果构造函数没有显式地调用超类构造函数,Java编译器会自动插入对超类的无参数构造函数的调用。”
在你的例子中,超类在构造时可能依赖于t的状态,你可以在以后请求一个副本。
这里有一个广泛的讨论和here

7qhs6swi

7qhs6swi4#

你可以定义一个静态的supplier lambda,它可以包含更复杂的逻辑。

public class MyClass {

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

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

aiazj4mn5#

允许使用第二个示例而不允许使用第一个示例的原因很可能是为了保持语言的整洁,而不是引入奇怪的规则。
允许任何代码在super被调用之前运行都是危险的,因为你可能会弄乱那些应该被初始化但还没有初始化的东西。(例如,调用一个静态方法来计算一些需要转到构造函数的东西),但是你永远不能使用尚未完全构造的对象中的任何东西,这是一件好事。

uurv41yg

uurv41yg6#

这是我的解决方案,允许创建额外的对象,修改它,而无需创建额外的类,字段,方法等。

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

gblwokeq7#

这就是Java的工作方式:-)选择这种方式有技术上的原因。在调用super之前不能对局部变量进行计算确实很奇怪,但在Java中,对象必须首先被分配,因此它需要一直到Object,以便在您可以修改它们之前正确初始化所有字段。
在你的例子中,大多数时候有一个getter允许你访问你给super()的参数。所以你可以使用这个:

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

rslzwgfq8#

这很有可能成为可能。目前,对超级构造函数的调用必须是构造函数中的第一条语句。有一个名为Statements before super()的JEP草案试图改变这一点。这个特殊的JEP草案于2023年1月开放,然而,讨论已经持续了几年。它是2018年this issue的延续,由Java的首席架构师Brian Goetz创建,所以我们有理由相信,既然有人花了时间来做这件事,它就会成功。
一个compiler implementation for this change已经完成了。一旦JLS被更新,在调用超级构造函数之前可以自由地使用语句,除了try {} catch {},因为它决定在这方面采取保守的方法。
因此,这将是可能的:

class Foo extends Bar {

    Foo() {
        System.out.println("Before super");
        super();
        System.out.println("After super");
    }
}

相关问题