Java中静态final字段的初始化

7
public class Main {
 static final int alex=getc();
 static final int alex1=Integer.parseInt("10");
 static final int alex2=getc();

public static int getc(){
    return alex1;
}

public static void main(String[] args) {
    final Main m = new Main();
    System.out.println(alex+" "+alex1 +" "+alex2);
  } 
}

有人能告诉我为什么会打印出 0 10 10 吗?我知道它是一个静态常量,值应该不会改变,但理解编译器如何初始化字段还是有一点困难。

5个回答

6

这是一个排序问题。静态字段的初始化顺序是按照它们被遇到的顺序进行的,所以当你调用getc()来初始化alex变量时,alex1还没有被设置。你需要先初始化alex1,然后你就会得到预期的结果。


这是一个面试问题,不是真正的问题,我试图理解为什么编译器会赋值为0,只是因为alex1字段尚未初始化...? 因为alex1是final的,并且alex取代了alex1=>>alex1是0? - Alexx
静态赋值与成员变量不同,并且没有这样的保证。每个静态赋值都按照类加载器找到它的顺序进行评估。因此,在您的情况下,它通过调用getc()首先分配了alex。在这个时间点上,alex1还没有被初始化,所以返回0。 - stevevls
如果在静态块中初始化了alex1字段,并赋值为10,那么其他字段会打印什么?静态块和初始化哪个先执行? - Alexx
@Alexx,它仍然按照出现的顺序进行初始化。静态块放置的位置是唯一需要考虑的。 - Peter Lawrey
再次强调,这取决于顺序。你的 static final int alex1=Integer.parseInt("10"); 实际上只是静态块的简写形式,编译器会把它转换成相同的形式。 - stevevls
@stevevis - 实际上,这不是类加载器在做这件事,而是内在的类初始化机制。(实际的初始化是由类的"<cinit>"伪方法执行的。) - Stephen C

5
这种情况在JLS 8.3.2.3“初始化期间字段使用的限制”中有所涉及。
JLS规则允许在您的问题中使用,并说明第一次调用getc()将返回alex的默认(未初始化)值。
然而,规则不允许某些未初始化变量的使用,例如:
int i = j + 1;
int j = i + 1;

被禁止。


关于其他答案的一些观点。这不是Java编译器“无法理解”的情况。编译器严格实现了Java语言规范所指定的内容。(或者换句话说,编译器 可以 被编写成检测您示例中的循环依赖并将其视为编译错误。但是,如果它这样做,它将拒绝有效的Java程序,因此不会是符合规范的Java编译器。)


在您的评论中,您说到:
“...最终字段总是在对象创建之前在编译时或运行时初始化。”
这是不正确的。
实际上有两种类型的final字段:
- 所谓的“常量变量”确实在编译时计算。(常量变量是指“原始类型或字符串类型的变量,它是final的,并使用编译时常量表达式进行初始化”-参见JLS 4.12.4)。这样的字段在访问它时将始终已被初始化...除了一些与此处无关的复杂情况。 - 其他final字段按照JLS指定的顺序进行初始化,并且可以在其初始化之前看到字段的值。 final变量的限制是它们必须在类初始化(对于静态变量)或对象初始化期间仅初始化一次。
最后,这种情况很大程度上是“边缘案例”行为。一个典型的书写良好的类在初始化之前不需要访问final字段。

4

未被编译器视为常量表达式的静态 final 字段会按声明顺序进行初始化。因此,在初始化 alex 时,alex1 尚未被初始化,因此 getc() 返回 alex1 的默认值(0)。

请注意,在以下情况下结果将不同(为 10 10 10):

static final int alex1 = 10;

在这种情况下,alex1是由编译时常量表达式初始化的,因此它从一开始就被初始化了。

+1 对于编译时常量表达式来说是非常有用的。(否则为什么要使用Integer.parseInt?) - Tom Hawtin - tackline

2

静态字段没有什么特别之处,只是编译器无法确定您是否在使用一个可以访问未初始化字段的方法。

例如:

public class Main {
    private final int a;

    public Main() {
        System.out.println("Before a=10, a="+getA());
        this.a = 10;
        System.out.println("After a=10, a="+getA());
    }

    public int getA() {
        return a;
    }

    public static void main(String... args) {
        new Main();
    }
}

打印

Before a=10, a=0
After a=10, a=10

0

类变量不需要初始化,它们会自动设置为默认值。对于基本类型(如int、short...),默认值是0(零),对于对象来说,默认值是null。 因此,alex1被设置为0。 方法变量必须初始化,否则会出现编译错误。

要获得更好的解释,请阅读http://download.oracle.com/javase/tutorial/java/javaOO/classvars.html


是的,但是最终字段必须在编译时或运行时进行初始化,然后才能创建对象。您所说的适用于普通字段,而不是最终字段。它可以正常工作,我之前说过它会打印 0 10 10 - Alexx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接