使用未初始化的最终字段 – 带/不带“this”。 预选赛
有人可以向我解释为什么以下两个样本中的第一个编译,而第二个不是? 注意唯一的区别是第一个显式限定了对'.this'的引用x,而第二个没有。 在这两种情况下,最终的字段x显然都是在初始化之前尝试使用的。
我会认为这两个样本将被完全平等对待,导致两者的编译错误。
1)
public class Foo { private final int x; private Foo() { int y = 2 * this.x; x = 5; } }
2)
public class Foo { private final int x; private Foo() { int y = 2 * x; x = 5; } }
经过一番专业阅读和思考后,我得出结论:
在Java 5或Java 6编译器中,这是正确的行为。 第16章“ Java语言规范的明确分配”第三版说:
每个局部variables(§14.4)和每个空白的
final
(§4.12.4)域(§8.3.1.2)都必须有一个明确赋值的值, 对其值的访问由简单赋值运算符=
的左操作数以外的expression式中简单的名称组成。
(强调我的)。 因此,在expression式2 * this.x
, this.x
部分不被视为“访问[ x
's]值”(因此不受this.x
明确赋值的规则),因为this.x
不是实例variablesx
简单名称。 (注意在明确赋值发生时的规则,在上面引用的文本之后的段落中, 确实允许类似this.x = 3
,并认为之后明确赋值x
;这只是访问规则不计数)请注意,在这种情况下, this.x
的值将根据§17.5.2为零。
在Java 7编译器中,这是一个编译器错误,但可以理解。 Java 7 SE版的Java语言规范的第16章“明确赋值”说:
每个局部variables( §14.4 )和每个空白的
final
字段( §4.12.4 , §8.3.1.2 )都必须有一个明确赋值的值,对其值的访问由简单赋值运算符的左侧操作数(除了简单赋值运算符的左侧操作数)外,在expression式的任何位置出现的variables的简单名称(或者,对于字段而言,由此限定的字段的简单名称) 15.26.1 )。
(强调我的)。 所以在expression式2 * this.x
, this.x
部分应该被认为是“访问[ x
's]值”,并且应该给出一个编译错误。
但是你没有问是否第一个应该编译,你问为什么编译(在一些编译器中)。 这是必然的猜测,但我会做两个猜测:
- 大多数Java 7编译器是通过修改Java 6编译器编写的。 一些编译器编写者可能没有注意到这种变化。 此外,许多Java-7编译器和IDE仍然支持Java 6,而且一些编译器编写者可能没有觉得有动机拒绝以Java-6模式接受的Java-7模式。
- 新的Java 7行为奇怪地不一致。 像
(false ? null : this).x
仍然是允许的,对于这个问题,甚至(this).x
仍然是允许的。 这只是特定的令牌序列this
加号.
加上受此变化影响的字段名称。 当然,这样的不一致已经存在于赋值语句的左边(我们可以写this.x = 3
,但不是(this).x = 3
),但是更容易理解:接受this.x = 3
作为禁止的其他结构的特殊许可情况obj.x = 3
。 允许这是有道理的。 但是,我认为拒绝2 * this.x
作为另外允许的构造2 * obj.x
特殊禁止情况是2 * obj.x
,因为(1)这个特殊的禁止情况通过添加括号很容易解决, (2)这种特殊的禁止情况在以前版本的语言中被允许,并且(3)我们仍然需要特殊的规则,即final
字段具有它们的默认值(例如0
代表int
),直到它们被初始化,像(this).x
这样的情况,以及像this.foo()
这样的情况,其中foo()
是一个访问x
的方法。 所以一些编译器作者可能不会有动机去做出这种不一致的变化。
其中任何一个都是令人惊讶的 – 我认为编译器编写者有关于规范的每一个变化的详细信息,并且根据我的经验,Java编译器通常相当好地坚持规范(不像某些语言,每个编译器都有它的自己的方言) – 但是,发生了什么事情,以上是我唯一的两个猜测。
当你在构造函数中使用this
时, 编译器会将x
看作this
对象的成员属性(默认初始化) 。 由于x
是int
,所以默认情况下初始化为0
。 这使编译器感到高兴,并且在运行时也能正常工作。
当你不使用this
,编译器直接在词法分析中使用x
声明,因此它会抱怨初始化( 编译时现象 )。
所以它的定义是this
,它使得编译器在编译过程中的词法分析过程中将x
分析为对象与直接属性的成员variables,并导致不同的编译行为。
当用作主expression式时,关键字this表示一个值,该值是对实例方法被调用的对象(第15.12节)的引用,或者是对被构造的对象的引用。
我认为编译器估计,编写this.x意味着“这个”存在,所以一个构造函数被调用(并且最终variables已经被初始化)。 但是当你试图运行它时,你应该得到一个RuntimeException
我假设你参考了Eclipse中的行为。 (正如评论一个与javac编译工程)。
我认为这是一个Eclipse问题。 它有自己的编译器和自己的一套规则。 其中之一是你可能不会访问一个没有初始化的字段,尽pipeJava-commpiler会为你初始化variables。