首先,一个难题:以下代码显示什么?
public class RecursiveStatic { public static void main(String[] args) { System.out.println(scale(5)); } private static final long X = scale(10); private static long scale(long value) { return X * value; } }
回答:
0
扰流板如下。
如果您打印X的规模(长),并重新定义X = scale(10) + 3,印刷品会X = 0那么X = 3。这意味着X暂时设置为0,后来又设置为3。这是违反final!
X
X = scale(10) + 3
X = 0
X = 3
3
final
静态修饰符与最终修饰符结合使用,还可以定义常量。最后的修饰符指示 此字段 的值 不能更改 。
来源:https : //docs.oracle.com/javase/tutorial/java/javaOO/classvars.html [添加了重点]
我的问题:这是一个错误吗?是final不明确的?
这是我感兴趣的代码。 X被分配了两个不同的值:0和3。我相信这是对的违反final。
public class RecursiveStatic { public static void main(String[] args) { System.out.println(scale(5)); } private static final long X = scale(10) + 3; private static long scale(long value) { System.out.println("X = " + X); return X * value; } }
这个问题被标记为Java静态最终字段初始化顺序的可能重复项。我相信这个问题 不是 重复的,因为另一个问题解决了初始化的顺序,而我的问题解决了与final标签结合的循环初始化。仅从另一个问题来看,我将无法理解为什么我的问题中的代码没有出错。
通过查看ernesto得到的输出,这尤其清楚:当a标记为时final,他得到以下输出:
a
a=5 a=5
这不涉及我的问题的主要部分:final变量如何更改其变量?
一个非常有趣的发现。要理解它,我们需要深入研究Java语言规范(JLS)。
原因是final只允许一次 分配 。但是,默认值为no Assignment 。实际上,每个 这样的变量 (类变量,实例变量,数组组件)从一开始就在 赋值 之前指向其 默认值 。然后,第一个分配更改参考。 __
看下面的例子:
private static Object x; public static void main(String[] args) { System.out.println(x); // Prints 'null' }
我们没有为明确分配值x,尽管它指向null,这是默认值。将其与§4.12.5进行比较:
x
null
变量的初始值 每个 类变量* ,实例变量或数组组件在 创建 时都会用 默认值 初始化(第15.9节,第15.10.2节) *
变量的初始值
每个 类变量* ,实例变量或数组组件在 创建 时都会用 默认值 初始化(第15.9节,第15.10.2节) *
请注意,这仅适用于此类变量,例如在我们的示例中。它不适用于局部变量,请参见以下示例:
public static void main(String[] args) { Object x; System.out.println(x); // Compile-time error: // variable x might not have been initialized }
在同一JLS段落中:
必须在使用 局部变量 (第14.4节,第14.14节)之前通过初始化(第14.4节)或赋值(第15.26节)为它 明确赋一个值 ,并且可以使用确定赋值规则(第§)进行验证。16(确定分配))。
现在final,从§4.12.4看:
最终 变量 可以将变量声明为 final 。甲 最终 变量可以仅 分配给一次 。如果将 最终 变量赋值给它,则是编译时错误,除非在 赋值之前绝对未赋值 最终 变量(第16节(确定赋值))。
最终 变量
可以将变量声明为 final 。甲 最终 变量可以仅 分配给一次 。如果将 最终 变量赋值给它,则是编译时错误,除非在 赋值之前绝对未赋值 最终 变量(第16节(确定赋值))。
现在回到您的示例,稍作修改:
public static void main(String[] args) { System.out.println("After: " + X); } private static final long X = assign(); private static long assign() { // Access the value before first assignment System.out.println("Before: " + X); return X + 1; }
它输出
Before: 0 After: 1
回想一下我们学到的东西。在方法assign的变量X是 没有分配 的值呢。因此,由于它是 类变量 ,因此它指向其默认值,并且根据JLS,这些变量总是立即指向其默认值(与局部变量相反)。在该assign方法之后,将为变量X分配值,1并且由于final我们无法再对其进行更改。因此,由于以下原因,以下操作将不起作用final:
assign
1
private static long assign() { // Assign X X = 1; // Second assign after method will crash return X + 1; }
感谢@Andrew,我找到了一个JLS段落,它完全涵盖了这种情况,它也演示了这一点。
但是首先让我们看一下
private static final long X = X + 1; // Compile-time error: // self-reference in initializer
为什么不允许这样做,而从方法中访问却是允许的呢?看看第8.3.3节,它讨论了如果尚未初始化字段时何时限制对字段的访问。
它列出了一些与类变量相关的规则:
对于简单地引用f在class或interface中声明的类变量的引用, 如果出现 以下情况C,则是 编译时错误 : * 该引用出现在(§8.7)的类变量初始化器C或静态初始化器中。和C 该引用显示在f自己的声明器的初始化程序中,或者出现在声明器的左侧f;和 * 该引用不在赋值表达式的左侧(第15.26节);和 包含引用的最里面的类或接口是C。
对于简单地引用f在class或interface中声明的类变量的引用, 如果出现 以下情况C,则是 编译时错误 :
f
C
* 该引用出现在(§8.7)的类变量初始化器C或静态初始化器中。和C
* 该引用不在赋值表达式的左侧(第15.26节);和
很简单,X = X + 1被这些规则捕获,方法访问不被捕获。他们甚至列出了这种情况并给出了一个例子:
X = X + 1
不以这种方式检查方法的访问,因此: class Z { static int peek() { return j; } static int i = peek(); static int j = 1; } class Test { public static void main(String[] args) { System.out.println(Z.i); } } 产生输出: 0 因为变量初始化器i使用类方法peek来访问变量的值,j然后j变量初始化器才对其进行了初始化,此时它 仍然具有其默认值 (第4.12.5节)。
不以这种方式检查方法的访问,因此:
class Z { static int peek() { return j; } static int i = peek(); static int j = 1; } class Test { public static void main(String[] args) { System.out.println(Z.i); } }
产生输出:
因为变量初始化器i使用类方法peek来访问变量的值,j然后j变量初始化器才对其进行了初始化,此时它 仍然具有其默认值 (第4.12.5节)。
i
j