Java 静态初始值设定项在定义字段之前无法引用该字段

Java 静态初始值设定项在定义字段之前无法引用该字段,java,static-initialization,static-initializer,Java,Static Initialization,Static Initializer,我有以下注释错误的代码 public final class MyStaticClass { private MyStaticClass() {} static { a = new A(); b = new B(a); // Cannot access a field before it is defined } private static final A a; private static final B

我有以下注释错误的代码

public final class MyStaticClass {

    private MyStaticClass() {}

    static {

        a = new A();
        b = new B(a);    // Cannot access a field before it is defined
    }

    private static final A a;
    private static final B b;
}
我对使用静态初始值设定项相当陌生,但我不知道为什么它不能编译。我看过一些关于这个主题的帖子,看到了初始化的顺序,但这似乎并没有违反规则。在初始化b时,a应该已经被初始化了


我有一个解决办法,就是将这个类设置为单例,但是这样做会使代码的可读性降低一点。我很想知道这里出了什么问题。

如果静态块还没有声明,就不能在静态块中使用
a
。所以在静态块之前声明它:

public final class MyStaticClass {

    private MyStaticClass() {}

    private static final A a;
    private static final B b;

    static {
        a = new A();
        b = new B(a);
    }
}


(我假设调用
A
的实例“b”和
b
的实例“A”是一个输入错误。)

静态初始化是按照写入代码的顺序完成的。因此,在您的代码中,它将首先进入静态块,然后进入变量声明

在声明变量之前不能使用它,这就是为什么它不能编译

public final class MyStaticClass {

       private static final A a;
        private static final B b;

    private MyStaticClass() {}

    static {

        a =  new A();
        b = new B(a);    // Cannot access a field before it is defined
    }


}



    }
这一点在本文中进行了解释。事实上,有几种方法可以解决这个问题

使用限定名
a

// #1
public final class MyStaticClass {
    static {
        a = new A();
        b = new B(MyStaticClass.a);
    }

    private static final A a;
    private static final B b;
}
如果
a
b
是在实例初始值设定项中初始化的实例字段,
a
可以限定为
this.a

将前向引用置于作业左侧的
a

// #2
public final class MyStaticClass {
    static {
        b = new B(a = new A());
    }

    private static final A a;
    private static final B b;
}
当然,将声明文本置于引用之前:

// #3
public final class MyStaticClass {
    private static final A a;
    private static final B b;

    static {
        a = new A();
        b = new B(a);
    }
}
根据JLS,#3在技术上不是必需的(“这些类变量在范围内”),而是设计用于捕捉字段初始化顺序错误的特定类型的错误:

public final class MyStaticClass {
    private static final B b = new B(a); // a is null
    private static final A a = new A();
}
(尽管我刚才向您展示了两种方法来阻止它,并且无论如何都会犯错误。)


我推荐1号或3号,因为2号有点深奥。您似乎没有犯此规则旨在捕捉的错误。

查看您的变量名,我想起了
public static final int ONE=2:P哎呀。很抱歉,我对这个评论更感到困惑。那么在类中物理上更高的位置声明字段意味着它可以编译?为什么?我以为整个文件在编译之前都被标记了。为什么A必须是B的超类?B有一个采用a类型的构造函数(B内部有一个对a的引用)。你在写变量时把事情弄糟了,这是我误解的。。。现在检查一下编辑,这确实有效。我刚才在另一篇评论中说,你知道这为什么有效吗?我认为编译器会在编译之前标记整个文件,因此它应该知道声明的字段是什么,而不考虑顺序。还有一个问题提到在调用初始值设定项之前初始化字段。你知道为什么更改顺序会影响编译器的结果吗?这是因为静态初始化是按照代码中写入的顺序进行的。您好,是的,这是一个片段,很抱歉,实际的代码是10行初始化。为什么移动声明会产生影响?我明白你的意思,但我认为java编译就像C#一样,首先对整个文件进行标记,然后进行编译。我认为init顺序是方法、字段,然后是初始值设定项,所以字段在文件后面的物理位置声明肯定不重要吗?我认为这是为了避免循环依赖,尽管我不确定在这种情况下它做了什么有益的事情。