我想到这个问题的具体用途如下,但它更加普遍。
我有一个自定义JFrame
类,它也可以作为其组件的ActionListener
。 所以我的构造函数如下所示:
private JButton myButton;
public MyCustomFrame() {
super();
myButton.addActionListener(this);
// ... more stuff
}
我的问题是,这在幕后实际上是如何运作的? 如果构造函数是"创建"this
引用的对象,如何在构造函数返回之前使用this
? 代码编译和工作得很好(据我所知(,所以对象在某种意义上必须已经"存在",但我担心这可能会导致不可预见的问题。 将"部分构造"引用传递给addActionListener()
(或只是对它执行任何逻辑(是否有任何危险? 还是有什么幕后魔术在幕后发生,让我安全?
例如,没有默认值且必须由构造函数提供的东西怎么办? 如果我private final String SOME_VALUE;
声明,我知道这应该默认为 null
,但在构造函数中为常量提供值之前,对象不应该完全形成。那么,尽管参考是最终的,但可能会有变化的值吗?
Java 语言规范指定了实例创建的步骤
[...]
接下来,为新类实例分配空间。如果有 空间不足,无法分配对象,评估类 实例创建表达式通过抛出 内存不足错误。
新对象包含 中声明的所有字段的新实例 指定的类类型及其所有超类。作为每个新字段 实例被创建,它被初始化为其默认值 (§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 内存模型规范中的 10 以外的值。