最终的定义不明确吗? [英] Is final ill-defined?

查看:96
本文介绍了最终的定义不明确吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,一个难题: 以下代码显示什么?

First, a puzzle: What does the following code print?

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

If you print X in scale(long) and redefine X = scale(10) + 3, the prints will be X = 0 then X = 3. This means that X is temporarily set to 0 and later set to 3. This is a violation of final!

static修饰符与final修饰符一起也用于定义常量. 最终修饰符表示此字段的值不能更改.

The static modifier, in combination with the final modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change.

来源: https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html [已添加重点]

Source: https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html [emphasis added]

我的问题: 这是一个错误吗? final定义不正确吗?

My question: Is this a bug? Is final ill-defined?

这是我感兴趣的代码. 为X分配了两个不同的值:03. 我认为这违反了final.

Here is the code that I am interested in. X is assigned two different values: 0 and 3. I believe this to be a violation of 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标签的循环初始化. 仅从另一个问题来看,我将无法理解为什么我的问题中的代码没有出错.


This question has been flagged as a possible duplicate of Java static final field initialization order. I believe that this question is not a duplicate since the other question addresses the order of initialization while my question addresses a cyclic initialization combined with the final tag. From the other question alone, I would not be able to understand why the code in my question does not make an error.

通过查看ernesto得到的输出,这一点尤其明显: 当afinal标记时,他得到以下输出:

This is especially clear by looking at the output that ernesto gets: when a is tagged with final, he gets the following output:

a=5
a=5

这不涉及我的问题的主要部分:final变量如何更改其变量?

which does not involve the main part of my question: How does a final variable change its variable?

推荐答案

一个非常有趣的发现.要理解它,我们需要深入研究Java语言规范( JLS )

A very interesting find. To understand it we need to dig into the Java Language Specification (JLS).

原因是final仅允许一项分配.但是,默认值为no assignment .实际上,每个这样的变量(类变量,实例变量,数组组件)都从开头指向赋值之前的默认值.然后,第一次分配会更改参考.

The reason is that final only allows one assignment. The default value, however, is no assignment. In fact, every such variable (class variable, instance variable, array component) points to its default value from the beginning, before assignments. The first assignment then changes the reference.

看看下面的例子:

private static Object x;

public static void main(String[] args) {
    System.out.println(x); // Prints 'null'
}

我们没有为x明确分配值,尽管它指向null,但这是默认值.将此内容与§4.12.5 :

We did not explicitly assign a value to x, though it points to null, it's default value. Compare that to §4.12.5:

变量的初始值

Initial Values of Variables

创建时,每个类变量,实例变量或数组组件均使用默认值初始化(

Each class variable, instance variable, or array component is initialized with a default value when it is created (§15.9, §15.10.2)

请注意,这仅适用于此类变量,例如在我们的示例中.它不适用于局部变量,请参见以下示例:

Note that this only holds for those kind of variables, like in our example. It does not hold for local variables, see the following example:

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§16(确定分配)).

A local variable (§14.4, §14.14) must be explicitly given a value before it is used, by either initialization (§14.4) or assignment (§15.26), in a way that can be verified using the rules for definite assignment (§16 (Definite Assignment)).


最终变量

现在,我们从


Final variables

Now we take a look at final, from §4.12.4:

最终变量

可以将变量声明为 final . final 变量只能被分配一次.如果将 final 变量分配给它,则将导致编译时错误,除非在分配之前立即绝对未分配该变量(

A variable can be declared final. A final variable may only be assigned to once. It is a compile-time error if a final variable is assigned to unless it is definitely unassigned immediately prior to the assignment (§16 (Definite Assignment)).


说明

现在回到您的示例,稍作修改:


Explanation

Now coming back to the your example, slightly modified:

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,以下操作将不起作用:

Recall what we have learned. Inside the method assign the variable X was not assigned a value to yet. Therefore, it points to its default value since it is an class variable and according to the JLS those variables always immediately point to their default values (in contrast to local variables). After the assign method the variable X is assigned the value 1 and because of final we can't change it anymore. So the following would not work due to final:

private static long assign() {
    // Assign X
    X = 1;

    // Second assign after method will crash
    return X + 1;
}


JLS中的示例

由于@Andrew,我找到了一个JLS段落,它完全涵盖了这种情况,并且也对此进行了演示.


Example in the JLS

Thanks to @Andrew I found a JLS paragraph that covers exactly this scenario, it also demonstrates it.

但是首先让我们看一下

private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer

为什么不允许这样做,而从该方法进行访问呢?看一下§8.3. 3 讨论如果尚未初始化字段时何时限制对字段的访问.

Why is this not allowed, whereas the access from the method is? Take a look at §8.3.3 which talks about when accesses to fields are restricted if the field was not initialized yet.

它列出了一些与类变量有关的规则:

It lists some rules relevant for class variables:

要简单地引用在类或接口C中声明的类变量f,如果 ,则是编译时错误:

For a reference by simple name to a class variable f declared in class or interface C, it is a compile-time error if:

  • 该引用显示在C的类变量初始化器或C的静态初始化器中(

  • The reference appears either in a class variable initializer of C or in a static initializer of C (§8.7); and

该引用显示在f自己的声明器的初始化程序中,或者出现在f声明器的左侧;和

The reference appears either in the initializer of f's own declarator or at a point to the left of f's declarator; and

引用不在赋值表达式的左侧(

The reference is not on the left hand side of an assignment expression (§15.26); and

包含引用的最里面的类或接口是C.

The innermost class or interface enclosing the reference is C.

很简单,X = X + 1被这些规则捕获,方法访问没有.他们甚至列出了这种情况并举了一个例子:

It's simple, the X = X + 1 is caught by those rules, the method access not. They even list this scenario and give an example:

不会以这种方式检查方法的访问,因此:

Accesses by methods are not checked in this way, so:

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 ).

because the variable initializer for i uses the class method peek to access the value of the variable j before j has been initialized by its variable initializer, at which point it still has its default value (§4.12.5).

这篇关于最终的定义不明确吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆