【问题标题】:Why can my instance initializer block reference a field before it is declared?为什么我的实例初始化程序块可以在声明之前引用一个字段?
【发布时间】:2018-01-11 21:10:19
【问题描述】:

我的理解是,你不能在声明变量之前引用它,并且在类的主体内但在任何方法之外的所有代码(包括实例初始化程序)都会在构造函数之前按顺序执行,当对象被创建(例外是static 变量和初始化块,它们在程序开始时按顺序运行,以初始化整个类)。那么,为什么下面的代码会编译(并运行!):

public class WhyIsThisOk {
    { a = 5; } // why is this ok???
    int a = 10;

    public WhyIsThisOk() {
    }

    public static void main(String[] args) {
        WhyIsThisOk why = new WhyIsThisOk();
        System.out.println(why.a); // 10
    }
}

【问题讨论】:

  • 您可能还应该写下它打印的内容(我猜是 5 个?)
  • 打印 10。因为这是最后一组

标签: java instance-initializers


【解决方案1】:

来自docs

Java 编译器将初始化程序块复制到每个构造函数中。 因此,这种方法可以用来在之间共享一个代码块 多个构造函数。

上面的说法有点误导,因为如果我们按照上面文档的解释,我们可以像这样重写原始代码:

public class WrongVersionOfWhyIsThisOk {

    int a = 10;

    public WhyIsThisOk (){
        a = 5;
    }

    public static void main(String[] args){
        WrongVersionOfWhyIsThisOk why = new WrongVersionOfWhyIsThisOk ();
        System.out.println(why.a);
    }
}

但是运行 WrongVersionOfWhyIsThisOk 将产生 5 而不是原始代码产生的 10。

但实际上是将初始化块和变量赋值都复制到构造函数中:

public class RightVersionOfWhyIsThisOk {

    int a;

    public RightVersionOfWhyIsThisOk (){
        a = 5;
        a = 10;
    }

    public static void main(String[] args){
        RightVersionOfWhyIsThisOk why = new RightVersionOfWhyIsThisOk ();
        System.out.println(why.a);
    }
}

更新:

这里是doc,详细描述了初始化顺序和构造函数调用:

4) 执行实例初始化器和实例变量 此类的初始化器,分配实例变量的值 对应实例变量的初始化器,在 它们在源代码中以文本形式出现的从左到右的顺序 为班级。如果执行这些初始化程序中的任何一个导致 异常,则不再处理任何初始化程序,这 过程突然完成,但出现同样的异常。除此以外, 继续第 5 步。

5) 执行此构造函数的其余部分。如果那个执行 突然完成,那么这个过程突然完成 同样的原因。否则,此过程正常完成。

【讨论】:

  • 这仍然很奇怪,因为它打印 10。这意味着“int a = 10;”出于声明目的,行被视为首先出现,但第二个执行 - 对此行为有什么解释吗?
  • 好的,经过您的编辑,这更有意义,您的再次成为最佳答案。我仍然没有看到任何“官方”文档说明这是如何发生的 - 正如您所提到的,您链接到的官方文档具有误导性。我很高兴看到声明保留在原处的规则的官方枚举,其余代码按顺序“拉”到构造函数的开头。
  • 如果第一个a=5 行从初始化程序块中取出,由于在声明之前被引用,代码无法编译,但在初始化程序块中它工作得很好。我想原因是“因为它是这样定义的”,但我想看看它是在哪里定义的。
【解决方案2】:

来自docs

8.3.2.3。初始化期间使用字段的限制

成员的声明需要以文本形式出现在它之前 仅当成员是一个实例(分别为静态)字段时才使用 一个类或接口 C 并且满足以下所有条件:

  • 使用发生在 C 的实例(分别为静态)变量初始化器或实例(分别为静态)初始化器中
    C.

  • 用法不在作业的左侧。

  • 用法是通过一个简单的名称。

  • C 是包含用法的最内层类或接口。

如果上述四个要求中的任何一个满足,则为编译时错误 没有遇到

在这种情况下,用法在赋值的左侧,所以它不是编译时错误。

【讨论】:

  • 正确答案,我开始写... [:-)
【解决方案3】:

实例初始化块在实例创建的时候被调用,所以创建why对象后正常工作。

初始化顺序为: 1) 静态块 2)构造函数 3)按出现顺序的实例块

【讨论】:

    【解决方案4】:

    只要调用任何构造函数(在构造函数的内容之前),就会执行初始化块的内容。

    因此,您可以提供对任何变量的引用,因为除非调用构造函数,否则它们不会被使用。对象已创建。

    【讨论】:

      【解决方案5】:

      声明的顺序并不重要。你也可以写:

      public  class WhyIsThisOk {
      
          {
              a = 5;
          }
      
          public WhyIsThisOk() {
          }
      
          public static void main(String[] args) {
              System.out.println(new WhyIsThisOk().a);
          }
      
          int a = 10;
      }
      

      重要的是,编译器首先复制(自上而下)a=5,然后将 a=10 复制到构造函数中,所以它看起来像:

      public WhyIsThisOk() {
          a = 5;
          a = 10;
      }
      

      最后看这个例子:

      public class WhyIsThisOk {
      
          {
              a = get5();
          }
      
          public WhyIsThisOk() {
              a = get7();
          }
      
          public static void main(String[] args) {
              System.out.println(new WhyIsThisOk().a);
          }
      
          int a = get10();
      
          public static int get5() {
              System.out.println("get5 from: " + new Exception().getStackTrace()[1].getMethodName());
              return 5;
          }
      
          public static int get7() {
              System.out.println("get7 from: " + new Exception().getStackTrace()[1].getMethodName());
              return 7;
          }
      
          public static int get10() {
              System.out.println("get10 from: " + new Exception().getStackTrace()[1].getMethodName());
              return 10;
          }
      }
      

      输出是:

      get5 from: <init>
      get10 from: <init>
      get7 from: <init>
      7
      

      【讨论】:

        猜你喜欢
        • 2018-07-16
        • 2016-07-21
        • 2018-07-08
        • 2015-09-15
        • 1970-01-01
        • 2019-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多