在Java中运行构造函数代码之前,字段是否已初始化?

Are fields initialized before constructor code is run in Java?

有人能解释一下以下程序的输出吗?我认为构造函数是在实例变量之前初始化的。所以我希望输出是"XZYY"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class X {
    Y b = new Y();

    X() {
        System.out.print("X");
    }
}

class Y {
    Y() {
        System.out.print("Y");
    }
}

public class Z extends X {
    Y y = new Y();

    Z() {
        System.out.print("Z");
    }

    public static void main(String[] args) {
        new Z();
    }
}


正确的初始化顺序是:

  • 如果类以前没有初始化过,静态变量初始化器和静态初始化块将按文本顺序进行初始化。
  • 在构造函数中调用super(),无论是显式的还是隐式的。
  • 实例变量初始化器和实例初始化块,按文本顺序。
  • super()后面的构造函数的剩余主体。
  • 参见JavaVirtualMealEngress规范的第2.2.5-6节。


    如果您查看类文件的反编译版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class X {
        Y b;

        X() {
            b = new Y();
            System.out.print("X");
        }
    }

    class Y {
        Y() {
            System.out.print("Y");
        }
    }

    public class Z extends X {

        Y y;

        Z() {
            y = new Y();
            System.out.print("Z");
        }

        public static void main(String args[]) {
            new Z();
        }
    }

    您可以发现实例变量y在构造函数中被移动,所以执行顺序如下

  • 调用EDOCX1[1]的构造函数
  • 触发X的默认构造函数
  • 调用X构造函数new Y()的第一行。
  • 打印Y
  • 打印X
  • 调用构造函数z new Y()中的第一行
  • 打印y
  • 打印Z
  • 所有实例变量都是使用构造函数语句初始化的。


    当调用构造函数时,实例变量初始值设定项在构造函数主体之前运行。你认为下面程序的输出是什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Tester {
        private Tester internalInstance = new Tester();
        public Tester() throws Exception {
            throw new Exception("Boom");
        }
        public static void main(String[] args) {
            try {
                Tester b = new Tester();
                System.out.println("Eye-Opener!");
            } catch (Exception ex) {
                System.out.println("Exception catched");
            }
        }
    }

    主方法调用测试人员构造函数,这会引发异常。您可能期望catch子句捕获此异常并打印捕获的异常。但是如果你试着运行它,你发现它什么也不做,就扔了一个StackOverflowError


    为了澄清静态的误解,我将简单地引用这段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Foo {
      { System.out.println("Instance Block 1"); }
      static { System.out.println("Static Block 1"); }
      public static final Foo FOO = new Foo();
      { System.out.println("Instance Block 2"); }
      static { System.out.println("Static Block 2 (Weird!!)"); }
      public Foo() { System.out.println("Constructor"); }
      static public void main(String p[]) {
        System.out.println("In Main");
        new Foo();
      }
    }

    令人惊讶的是,输出如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Static Block 1
    Instance Block 1
    Instance Block 2
    Constructor
    Static Block 2 (Weird!!)
    In Main
    Instance Block 1
    Instance Block 2
    Constructor

    注意,我们有一个static {},它是在两个实例{}之后调用的。这是因为我们在中间插入构造函数,第一次调用构造函数时插入执行顺序。

    当我研究这个答案时发现了这个-https://stackoverflow.com/a/30837385/744133。

    基本上,我们观察到这种情况会发生:

  • 在第一次初始化对象时,初始化静态和实例初始化的当前对象,根据发生顺序混合初始化

  • 对于所有接下来的初始化,只按照发生顺序进行实例初始化,因为静态初始化已经发生。

  • 我需要研究继承的混合,以及对super的显式和隐式调用,这将如何影响这一点,并将随着发现而更新。它可能与其他提供的答案类似,只是它们在静态初始化时出错了。


    初始化顺序在JLS 12.5中规定:

    1.首先,为新对象分配内存

    2.然后对象中的所有实例变量(包括这个类中定义的变量及其所有超类)都初始化为它们的默认值。

    3.最后调用构造函数。

    https://stackoverflow.com/questions/26552799/which-run-first-default-values-for-instance-variables-or-super-constructors