实例初始值设定项和 *this* 关键字

2022-09-01 19:27:09

尝试编译这段代码

public class Main {

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

    { System.out.println(x); } //Error here

    int x=1;
}

产生错误。但是,如果我将初始值设定项行更改为cannot reference a field before it is defined

    { System.out.println(this.x); }

它像一个超级按钮一样工作,打印默认的int值0

这对我来说有点令人困惑,为什么会有所不同?在这种情况下,它不应该是多余的吗?谁能解释一下幕后发生了什么,以清楚地说明它是如何工作的?this

PS:我知道通过在初始值设定项之前声明也会使它工作。x


答案 1

我将尝试在编译器层上进行解释。

假设你有一个这样的方法:

int x;
x = 1;
System.out.println(x);

编译将成功并执行。如果将方法更改为:

System.out.println(x);
int x;
x = 1;

它甚至不会编译与你给定的示例相同的编译。

编译器将初始化程序的代码复制到 和 初始化中。{ }ctorx=1

正如你所说,如果你在初始化器之前设置它,它的工作原理。x=1{ }

public class MainC {

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

    int x=1;
    {
      System.out.println(x);
    }
}

请参阅以下 Java 字节码:

  public MainC();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field x:I
         9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        12: aload_0
        13: getfield      #2                  // Field x:I
        16: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        19: return
      LineNumberTable:
        line 1: 0
        line 7: 4
        line 9: 9
        line 10: 19

该字段在调用中使用之前声明并获取值。x1System.out.println

那么为什么它不起作用,如果你设置它之后,从同样的原因你不能使用我的第二个例子的代码。该字段是在用法之后声明的,这毫无意义。{ }

那么为什么它与关键字一起使用?!this

让我们看一些代码:

public class Main {

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

    { System.out.println(this.x); } //Error here

    int x=1;
}

ctor 的相应 Java 字节码:

  public Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: aload_0
         8: getfield      #3                  // Field x:I
        11: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        14: aload_0
        15: iconst_1
        16: putfield      #3                  // Field x:I
        19: return
      LineNumberTable:
        line 1: 0
        line 7: 4
        line 9: 14

那么这里发生了什么呢?简单来说,关键字将 Main 对象引用加载到堆栈上。之后,可以访问字段 x,以便可以成功执行调用。thisSystem.out.println


答案 2

JSL 8.6 应该会解释你的编译时错误:

允许实例初始值设定项通过关键字 this (§15.8.3) 引用当前对象...

使用其声明在使用后以文本方式显示的实例变量有时会受到限制,即使这些实例变量在作用域内也是如此。请参阅 §8.3.3,了解管理对实例变量的前向引用的确切规则。

§8.3.3中,它说:

使用其声明在使用后以文本方式显示的实例变量有时会受到限制,即使这些实例变量在作用域内也是如此。具体来说,如果满足以下所有条件,则为编译时错误:

  • 类或接口 C 中实例变量的声明在使用实例变量后以文本方式出现;

  • 该用法是 C 的实例变量初始值设定项或 C 的实例初始值设定项中的简单名称;

  • 使用不在作业的左侧;

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

这就是为什么编写简单名称会给您带来错误的原因。x