Lit*_*per 184 java final class-variables static-initialization
首先,一个难题:以下代码打印什么?
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;
}
}
Run Code Online (Sandbox Code Playgroud)
回答:
0
下面的剧透.
如果您打印X的规模(长),并重新定义X = scale(10) + 3,印刷品会X = 0那么X = 3.这意味着X暂时设置为0以后设置为3.这是违反final!
static修饰符与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;
}
}
Run Code Online (Sandbox Code Playgroud)
此问题已被标记为Java静态最终字段初始化顺序的可能重复.我相信这个问题并不重复,因为另一个问题解决了初始化的顺序,而我的问题解决了与final标签结合的循环初始化.仅从另一个问题来看,我无法理解为什么我的问题中的代码不会出错.
通过查看ernesto得到的输出特别清楚:当a标记时final,他得到以下输出:
a=5
a=5
Run Code Online (Sandbox Code Playgroud)
这不涉及我的问题的主要部分:final变量如何改变其变量?
Zab*_*uza 216
一个非常有趣的发现.为了理解它,我们需要深入研究Java语言规范(JLS).
原因是final只允许一项任务.但是,默认值不是赋值.事实上,每个这样的变量(类变量,实例变量,数组组件)在分配之前从头开始指向其默认值.然后第一个分配更改引用.
看一下下面的例子:
private static Object x;
public static void main(String[] args) {
System.out.println(x); // Prints 'null'
}
Run Code Online (Sandbox Code Playgroud)
我们没有明确地指定一个值x,尽管它指向null它,它是默认值.与§4.12.5相比:
变量的初始值
请注意,这仅适用于那些变量,例如我们的示例.它不适用于局部变量,请参阅以下示例:
public static void main(String[] args) {
Object x;
System.out.println(x);
// Compile-time error:
// variable x might not have been initialized
}
Run Code Online (Sandbox Code Playgroud)
来自相同的JLS段落:
一个局部变量(§14.4,§14.14)必须明确给出的数值在使用之前,由要么初始化(§14.4)或分配(§15.26),在可以使用明确赋值的规则进行验证的方式(§ 16(确定任务)).
现在我们来看看final,从§4.12.4:
最终变量
变量可以声明为final.甲最终变量可以仅分配给一次.如果指定了最终变量,则它是编译时错误,除非它在赋值之前明确地未分配(§16(Definite Assignment)).
现在回到你的例子,稍加修改:
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;
}
Run Code Online (Sandbox Code Playgroud)
它输出
Before: 0
After: 1
Run Code Online (Sandbox Code Playgroud)
回想一下我们学到的东西.在方法assign的变量X是没有分配的值呢.因此,它指向其默认值,因为它是一个类变量,根据JLS,这些变量总是立即指向它们的默认值(与局部变量相反).在assign方法之后,变量X被赋值1,因为final我们不能再改变它.因此,以下内容不起作用final:
private static long assign() {
// Assign X
X = 1;
// Second assign after method will crash
return X + 1;
}
Run Code Online (Sandbox Code Playgroud)
感谢@Andrew我找到了一个JLS段落,它完全涵盖了这个场景,它也展示了它.
但首先让我们来看看
private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer
Run Code Online (Sandbox Code Playgroud)
为什么这不允许,而方法的访问是?看一下§8.3.3,它讨论了如果字段尚未初始化则何时限制对字段的访问.
它列出了一些与类变量相关的规则:
对于通过简单名称引用
f类或接口中声明的类变量C,如果出现以下情况,则为编译时错误:
它很简单,X = X + 1被这些规则捕获,方法访问不是.他们甚至列出了这个场景并给出了一个例子:
不以这种方式检查方法的访问,因此:
Run Code Online (Sandbox Code Playgroud)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); } }产生输出:
Run Code Online (Sandbox Code Playgroud)0因为变量初始化器
i使用类方法peek来访问变量的值j之前j已经由其变量初始化器初始化,此时它仍然具有其默认值(§4.12.5).
Sur*_*tta 22
这与决赛没什么关系.
由于它是实例级或类级别,如果尚未分配任何内容,它将保留默认值.这就是你0在没有分配时访问它的原因.
如果您在X未完全分配的情况下进行访问,则它将保留long的默认值0,即结果.
Old*_*eon 20
不是错误.
从第一次调用时scale调用
private static final long X = scale(10);
Run Code Online (Sandbox Code Playgroud)
它试图评估return X * value.X尚未分配值,因此long使用了a的默认值(即0).
因此,该行代码的计算结果为X * 10,即0 * 10是0.
Eug*_*ene 14
这根本不是一个错误,简单地说它不是一种非法形式的前向引用,仅此而已.
String x = y;
String y = "a"; // this will not compile
String x = getIt(); // this will compile, but will be null
String y = "a";
public String getIt(){
return y;
}
Run Code Online (Sandbox Code Playgroud)
它只是规范允许的.
举个例子,这正是匹配的地方:
private static final long X = scale(10) + 3;
Run Code Online (Sandbox Code Playgroud)
你是做一个向前引用到scale像以前一样说,是不以任何方式违法,但可以让你获得的默认值X.再次,这是规范允许的(更确切地说,它是不被禁止的),所以它工作得很好