为什么我的实例初始化程序块可以在声明之前引用一个字段?

Why can my instance initializer block reference a field before it is declared?

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

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
    }
}

来自docs

The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.

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

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) Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.

5) Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.

在实例创建时调用实例初始化块time.so在创建 why 对象后正常工作。

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

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

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

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

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

来自docs

8.3.2.3. Restrictions on the use of Fields during Initialization

The declaration of a member needs to appear textually before it is used only if the member is an instance (respectively static) field of a class or interface C and all of the following conditions hold:

  • The usage occurs in an instance (respectively static) variable initializer of C or in an instance (respectively static) initializer
    of C.

  • The usage is not on the left hand side of an assignment.

  • The usage is via a simple name.

  • C is the innermost class or interface enclosing the usage.

It is a compile-time error if any of the four requirements above are not met

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