如何在构造函数结束之前引用/处理“this”?

我想到这个问题的具体用法如下,但它更加普遍。

我有一个自定义的JFrame类,它也可以作为其组件的ActionListener 。 所以我的构造函数看起来像下面这样:

 private JButton myButton; public MyCustomFrame() { super(); myButton.addActionListener(this); // ... more stuff } 

我的问题是,这实际上是如何在幕后工作的? 如果构造函数是“创建”由此引用的对象,那么在构造函数返回之前如何使用this ? 代码编译并且工作完全正常(据我所知),因此对象必须已经在某种意义上“存在”,但我担心这可能会导致无法预料的问题。 传递对addActionListener()的“部分构造”引用是否存在任何危险(或者通常只使用它执行任何逻辑)? 或者是否有一些让我安全的幕后魔术?

例如,那些没有默认值但必须由构造函数提供的东西呢? 如果我有private final String SOME_VALUE; 声明,我明白这应该默认为null ,但是在构造函数中提供一个值之前,该对象不应该完全形成。 那么参考文献尽管是最终的,但可能会有不断变化的价值吗?

Java语言规范指定了实例创建的步骤

[…]

接下来, 为新的类实例分配空间 。 如果没有足够的空间来分配对象,则通过抛出OutOfMemoryError突然完成对类实例创建表达式的求值。

新对象包含在指定类类型及其所有超类中声明的所有字段的新实例。 在创建每个新字段实例时,会将其初始化为其默认值(第4.12.5节)。

接下来,从左到右评估构造函数的实际参数。 如果任何参数评估突然完成,则不会计算其右侧的任何参数表达式,并且出于同样的原因,类实例创建表达式会突然完成。

接下来, 调用指定类类型的选定构造函数。 这导致为类类型的每个超类调用至少一个构造函数。 此过程可以由显式构造函数调用语句(第8.8节)指导,并在第12.5节中详细描述。

因此,当调用构造函数(这是一个方法)时,您的实例存在默认值。

对于final字段,如果您尝试访问它们,它们似乎也是默认的。 例如

 public class Driver { public static void main(String[] args) { new Driver(); } final int value; public Driver() { print(this); value = 3; } static void print(Driver driver) { System.out.println(driver.value); } } 

将打印0.如果我能找到它,我会马上回到JLS条目。

我找不到更具体的东西,然后是上面的东西。 也许在4.12.4。 final变量

最终变量只能分配一次。

您可以认为默认初始化将值设置为0或null ,并且赋值会更改它。

一旦调用构造函数,您的对象从一开始就已经存在,而您只是用值填充它。

如果您传递对象的方法试图使用尚未在构造函数中声明的值,则会出现这种危险。


您还希望避免让构造函数(以及其他方法)以构造函数的用户不期望的方式运行。

如果实例化对象的人没有理由期望构造函数自动将该对象绑定到按钮,那么您可能不应该这样做。

this确实存在于构造函数完成之前。 但是,在构造函数完成之前允许引用this对象以逃避对象可能会带来危险。

如果您this引用传递给假设对象已完全形成并准备好的方法,该怎么办? 也许这对你的对象来说很好,但在很多情况下,这可能很危险。 允许其他方法在准备好使用之前访问该对象会对您的程序能够可靠地运行构成严重威胁。

你是绝对正确的,这是一件坏事,因为this可能只是在你使用它的时候被部分初始化了。

这就是许多编译器会发出警告的原因。

不要从构造函数中转义它,因为如果另一个线程读取了构造尚未完成的实例的变量,则该线程可能会读取意外值。

下面是一个例子。

 public class A { private final int value; public A(int value) { this.value = value; new Thread(new Runnable() { // this escape implicitly public void run() { System.out.println(value); } }).start(); } public static void main(String[] args) { new A(10); } } 

该程序可能会显示Java Memory Model规范中的10以外的值。