4

在 The Java Tutorials - Initializing Fields中,有关于实例初始化块(Instance Initializer)的描述:

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

如果释义正确,则为以下代码:</p>

public class ConstructorTest {

    public static void main(String[] args) {
        Child c = new Child();
    }
}

class Parent {
    Parent() {
        System.out.println("Parent non-argument Constructor");
    }
}

class Child extends Parent {

    {
        System.out.println("Child Instance Initialization Block");
    }

    Child() {
        this(2);
        System.out.println("Child no-argument Constructor");

    }

    Child(int i) {
        this(10, i);
        System.out.println("Child 1-argument Constructor");
    }

    Child(int i, int j) {
        System.out.println("Child 2-argument Constructor");
    }
}

输出应该是:</p>

Parent non-parm Constructor
Child Instance Initialization Block
Child 2-argument Constructor
Child Instance Initialization Block
Child 1-argument Constructor
Child Instance Initialization Block
Child no-argument Constructor

但实际输出的是:</p>

Parent non-argument Constructor
Child Instance Initialization Block
Child 2-argument Constructor
Child 1-argument Constructor
Child no-argument Constructor

是我误解了那句话的意思,还是描述不够准确?</p>

还有一个关于显式构造函数调用的疑问:

基于两个基础:</p>

  • 如果存在,另一个构造函数的调用必须是构造函数的第一行。
  • 在一个构造函数内部,使用 this() 调用另一个构造函数,使用 super() 直接调用超类对应的构造函数。

MEANS 在子类的构造函数中使用 this() 是否会隐式删除对超类的无参数构造函数的默认调用?

谢谢回复。

4

3 回答 3

5

编辑:事实证明 JLS 毕竟是准确的,尽管它很难阅读。这在第 12.5 节中有详细说明:

就在对新创建对象的引用作为结果返回之前,使用以下过程处理指示的构造函数以初始化新对象:

  1. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。

  2. 如果此构造函数以同一类中另一个构造函数的显式构造函数调用(第 8.8.7.1 节)开始(使用 this),则评估参数并使用这五个相同的步骤递归地处理该构造函数调用。如果该构造函数调用突然完成,则此过程出于相同原因而突然完成;否则,继续执行步骤 5

  3. 此构造函数不以显式构造函数调用同一类中的另一个构造函数开始(使用 this)。如果此构造函数用于 Object 以外的类,则此构造函数将以显式或隐式调用超类构造函数(使用 super)开始。使用这五个相同的步骤递归地评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则此过程出于相同的原因突然完成。否则,继续执行步骤 4。

  4. 执行该类的实例初始化程序和实例变量初始化程序,将实例变量初始化程序的值分配给相应的实例变量,按照它们在源代码中以文本形式出现的从左到右的顺序。如果执行这些初始化程序中的任何一个导致异常,则不会处理更多初始化程序,并且此过程会突然完成相同的异常。否则,继续执行步骤 5。

  5. 执行此构造函数的其余部分。如果该执行突然完成,则此过程出于同样的原因突然完成。否则,此过程正常完成。

注意突出显示的部分 - 链式构造函数被执行,然后我们跳过第 4 步,这将执行实例初始化程序

现实是实例和字段初始化器只执行一次,从输出中可以看出。

非正式地,我认为将程序描述为是准确的:

  • 继续在同一个类 ( this(...)) 中链接构造函数,直到到达不以 . 开头的构造函数主体this
  • 执行相应的超级构造函数
  • 执行实例变量初始化器和实例初始化器
  • 执行“最里面”构造函数的主体
  • 继续弹出构造函数体的堆栈,直到你最终得到“入口”构造函数

MEANS 在子类的构造函数中使用 this() 是否会隐式删除对超类的无参数构造函数的默认调用?

是的。类中的构造函数链中的某个位置,可以保证最终得到一个super隐式或显式调用的构造函数。这是唯一被调用的超类构造函数。

编辑:请注意,您引用的教程显然是不正确的。

示例类:

public class Test {
    {
        System.out.println("Foo");
    }
    
    public Test() {
    }
    
    public Test(int i)  {
        this();
    }
}

输出javap -c

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1  // Method java/lang/Object."<init>": ()V
       4: getstatic     #2  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #3  // String Foo
       9: invokevirtual #4  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: return

  public Test(int);
    Code:
       0: aload_0
       1: invokespecial #5                  // Method "<init>":()V
       4: return
}

如您所见,构造函数中没有Test(int)编译实例构造函数的代码。

基本上,只有直接调用超类构造函数的构造函数才会将实例初始化代码复制到其中。当然,所有其他构造函数最终将导致实例初始化程序代码通过调用超类构造函数的构造函数执行。

于 2012-09-03T20:38:53.523 回答
0

当您实例化类的对象时,初始化块只运行一次(使用任何构造函数 <= 这是您的疑问)。当类被类加载器加载时,静态初始化块只运行一次。

MEANS 在子类的构造函数中使用 this() 是否会隐式删除对超类的无参数构造函数的默认调用?

不,this() 将调用同一类的另一个构造函数,在这种情况下是默认构造函数(如果存在)。此构造函数(默认构造函数)将调用 super()。

于 2012-09-03T20:38:21.230 回答
0

我认为Java语言规范更准确:http ://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6

在创建类的实例时执行在类中声明的实例初始化程序

并在http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.5步骤 4 和 5 中描述您观察到的输出:

4. 执行此类的实例初始化程序和实例变量初始化程序...

5. 执行此构造函数的其余部分...

于 2012-09-03T20:53:21.650 回答