【问题标题】:How can "this" be referenced/processed before the constructor has concluded?在构造函数结束之前如何引用/处理“this”?
【发布时间】:2013-12-16 19:26:23
【问题描述】:

我想到这个问题的具体用法如下,不过笼统的多。

我有一个自定义的JFrame 类,它也用作其组件的ActionListener。所以我的构造函数如下所示:

private JButton myButton;

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

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

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

【问题讨论】:

标签: java constructor this


【解决方案1】:

那个Java语言规范规定了instance creation的步骤

[...]

接下来,为新的类实例分配空间。如果有 分配对象的空间不足,评估类 实例创建表达式通过抛出一个突然完成 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 Variables

最终变量只能分配一次。

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

【讨论】:

  • 那些没有默认值并且必须由构造函数提供的东西呢?例如,如果我声明了private final String SOME_VALUE;,我知道这应该默认为null,但在构造函数中为常量提供值之前,该对象不应该完全形成。那么,尽管引用是final,但该引用是否可能具有变化的值? (我会将此添加到问题中。)
  • @Jeff 在分配final 字段之前,我尝试编译一个释放this 引用的程序,它是默认值(0 或null)。 JLS 中一定有这方面的内容,我只需要找到它。
  • 我怀疑规范对此有任何说明,因为它是紧急行为。该字段尚未由构造函数分配,因为构造函数是像往常一样按顺序运行的代码。您在分配任何变量(最终或非最终变量)之前泄漏了this,Java 无法神奇地知道您要放在那里的值。
  • @TimB 并不神奇,但我在想可能存在一些编译器废话,可能会重新排列指令,特别是如果您使用字符串文字等常量值填充它。很有趣。
  • @Jeff 如果您在分配之前尝试在构造函数中使用final 字段,那么是的,它会引发编译器错误。但如果你泄露了引用,它就不会。
【解决方案2】:

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

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


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

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

【讨论】:

    【解决方案3】:

    this 在构造函数完成之前确实存在。但是,在构造函数完成之前允许对this 的引用转义您的对象可能会带来危险。

    如果您将this 引用传递给假定您的对象已完全形成并准备就绪的方法怎么办?对于您的对象,这可能很好,但在许多情况下,这可能很危险。在对象准备好使用之前允许其他方法访问对象将对您的程序能否可靠运行造成严重威胁。

    【讨论】:

      【解决方案4】:

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

      这就是为什么许多编译器会给出警告的原因。

      【讨论】:

        【解决方案5】:

        不要从构造函数中转义 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 以外的值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-04-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多