Java:为什么在定义字段之前引用字段时没有警告?
在定义或初始化静态字段之前,不能引用静态字段:
static Integer j = i; /* compile error */ static final Integer i = 5;
但是,当从实例初始化块(在匿名内部类中)引用它时,甚至不会生成警告。
见例子:
class StaticInitialization { static final Object o = new Object() {{ j = i; }}; static Integer j, k; static final Integer i = 5; static final Object o2 = new Object() {{ k = i; }}; }
结果是: j == null
, k == 5
,所以很明显我们已经做了引用,命令很重要,没有警告或编译错误。
这段代码合法吗?
这段代码合法吗? 大概。 我不认为编译器的工作是分析你在对静态变量进行修改时故意的对象实例化的副作用。
对来自同一类别中其他静力学的“在引用之前声明”的有限分析实际上只是对抗最常见的嘘声的帮助,而不是对间接错误的铁定保证。
我真的不会感到惊讶的是,“在引用之前声明”分析的范围有限,无法在其他static
声明中直接访问静态变量。 这是一个简单而紧凑的分析,具有最小的复杂性和非常快速。
扩展它以考虑对象实例化和方法调用的副作用OTOH,需要20-1000倍的重量和范围的静态程序分析 。 静态分析需要访问潜在的整个编译程序代码,使用基于约束的计算来确定可能发生的情况,以及可能在分钟内运行的时间。
鉴于这些选择,在Java语言设计师的手中,选择简单的分析只能覆盖同一类中的字段的直接访问。
在第一个块中,您没有在初始化之前引用静态字段,而是在定义之前引用它(如编译器将告诉您的那样)。 这将起作用,例如:
static Integer j; static final Integer i = j;
未显式设置时,所有字段都具有默认值(对于对象,这是null
引用,对于基元,这是相应的合理默认值, 0
, false
等)。 因此,字段始终初始化,因此编译器不需要检查它。
关于final
:它实际上是一个用于开发人员利益的修饰符。 正如JSL所述 , final
字段必须在访问之前“明确分配”。 这并不意味着它们没有其他值(如果它不是final
),它只是意味着编译器保护您不会明确地分配它,如果它找不到该赋值。
因此,对于非final
字段,您绝对可以在明确赋值之前引用它们。 只需粘贴上面的代码段即可validation。
您的代码与此类似:
class StaticInitialization { static final Foo1 o = new Foo1(); static Integer j, k; static final Integer i = 5; static final Foo2 o = new Foo2(); class Foo1 { public Foo1() { j = i; } } class Foo2 { public Foo2() { k = i; } } }
当你在Foo1
引用i
, i
是null。 但是,当你在Foo2
引用它时, i
变为5
。 注意,如果i
是一个编译常量( i
是int
而不是Integer
),那么j
将是5。
请参阅此相关问题: 以静态方式创建对象
final
变量的规则与其他变量完全不同。 例如,对于final
变量,编译器必须检查变量是否已明确分配一次,之后未分配。
这是可能的,因为对final
变量有很多限制(这几乎是final
变量)。
因此,对“在使用前分配”的深入分析只能在有限的最终变量世界中起作用。
这在JLS 8.3.2.3中定义。
成员声明只有在成员是实例时才需要以文本方式显示。
这就是你这样做时出错的原因..
static Integer j = i; /* compile error */ static final Integer i = 5;
但是不会以相同的方式检查方法的访问。
是的,这是因为首先分配所有变量的空间(并用#0初始化),然后按照代码的顺序完成所有初始化。
意思是,如果你改变这样的代码:
class StaticInitialization { static Integer j,k; static final Integer i = 5; static final Object o = new Object(){{ j = i; }}; static final Object o2 = new Object(){{ k = i; }}; }
结果是所有variables == 5
。