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


79

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

谢谢。


3
出于什么可能的原因将其标记为gwt?因为您在gwt中尝试过它?

2
TextBox是GWT类,但不,我想这与它无关。
Stephen Cagle 2010年

Answers:


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 2010年

3
这是一个非常方便的模式,几乎可以让您完全规避super()在构造函数中不会出现的要求。在极端情况下,您甚至可以将多个私有构造函数链接在一起,尽管我无法想到需要这样做的任何情况,如果我这样做了,我可能会开始怀疑是否有更好的方法来完成我的工作正在做。
劳伦斯·贡萨尔维斯

对。其他人已经回答了为什么它在Java中如此工作,我只想指出一个特殊的解决方法。我认为,对于您可以提出的其他大多数有效案例,也可以提出一个可以接受的解决方法。现在,与我自己矛盾的是,有一种情况让我有些不适:我正在扩展一个其构造函数具有Class参数的类。我想打电话给我super(this.getClass()),但是由于不允许您引用this,所以该代码是非法的,即使此时我应该能够知道实际的类似乎是完全合理的。
蜡翼

@waxwing-看起来“完全合理”,但是getClass()在JLS中将方法视为特殊情况。使用<classname>.class代替。
史蒂芬C

<classname>.class在这种情况下,使用选项不是一种选择,因为我正在编写一个人们可以扩展的抽象类,并且我需要实例的实际类。
蜡翼

32

在超级调用之前,我在计算上也遇到了同样的问题。有时您想在致电之前检查一些条件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];另请参阅此问答
垃圾神

如果构造子类对象需要“获取资源;构造超类对象;释放资源”模式,应如何实现?向内部构造函数传递一个实现单接口方法的对象,以便在发生故障时使用?大概可行,也许是以链结一些额外级别的构造函数调用为代价的。
supercat

我很乐于修复超类或使用合成。如果您提出这个问题,请ping我。
垃圾神

1

您可以定义静态供应商lambda,其中可以包含更复杂的逻辑。

public class MyClass {

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

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

0

允许使用第二个示例而不使用第一个示例的原因最有可能使语言保持整洁,并且不会引入奇怪的规则。

在调用super之前允许任何代码运行是很危险的,因为您可能会弄乱本应初始化但还没有初始化的内容。基本上,我想您可以在对super本身的调用中做很多事情(例如,调用静态方法来计算一些需要传递给构造函数的东西),但是您永远无法使用not中的任何东西。 -完全构造好的对象,这是一件好事。


0

这就是Java的工作方式:-)以这种方式选择Java是有技术原因的。确实可能很奇怪,您不能在调用super之前对本地进行计算,但是在Java中必须首先分配对象,因此它需要一直进行到Object,以便正确地初始化所有字段,然后才可以意外修改。他们。

在您的情况下,大多数情况下会使用getter来访问您提供给super()的参数。因此,您将使用以下代码:

超级(新的TextBox());
最终的TextBox框= getWidget();
...做你的事...

2
实际上,在Java中(与IIUC,C ++不同)不能保证“所有字段都已正确初始化”,因为超类构造函数可以调用final子类覆盖的非实例方法。这样,重写将看不到子类中的初始化字段(甚至final包含实例初始化程序的字段!)。一些静态分析工具会正确警告这种情况。
Jesse Glick

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());
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.