【问题标题】:Initialization of static final fields in JavaJava中静态final字段的初始化
【发布时间】:2011-05-13 08:58:01
【问题描述】:
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?我知道它是一个静态最终变量,它的值不应该改变,但理解编译器如何初始化字段有点困难。

【问题讨论】:

    标签: java initialization field final


    【解决方案1】:

    这是一个排序问题。静态字段按照遇到的顺序进行初始化,因此当您调用 getc() 来初始化 alex 变量时,alex1 尚未设置。你需要先初始化alex1,然后你会得到预期的结果。

    【讨论】:

    • 这是一个面试问题,并不是一个真正的问题,我试图理解为什么编译器分配 0 只是因为 alex1 字段还没有被初始化......?因为 alex1 是最终的,而 alex 需要alex1 =>>alex1 的位置是 0 ?
    • 静态赋值不同于成员变量并且没有这样的保证。每个静态分配都会被评估,以便类加载器找到它。因此,在您的情况下,它首先通过调用 getc() 分配 alex。此时alex1还没有初始化,所以返回0。
    • 好的,thx.Another question..如果它在alex1字段的静态块中初始化,值为10,其他人会打印什么?哪个先执行?静态块还是初始化?
    • @Alexx,它仍然按照它出现的顺序进行初始化。放置静态块的位置很重要。
    • 再次,这取决于订单。你的 static final int alex1=Integer.parseInt("10"); 实际上只是一个静态块的简写,编译器会变成相同的。
    【解决方案2】:

    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 变量的限制是它们必须在类初始化期间(对于static)或对象初始化期间初始化一次且仅一次。


    最后,这些东西是非常“极端”的行为。一个典型的写得很好的课程不需要 在初始化之前访问 final 字段。

    【讨论】:

      【解决方案3】:

      值不是编译时常量表达式的静态最终字段按声明顺序初始化。因此当alex被初始化时,alex1还没有被初始化,所以getc()返回默认值alex1(0)。

      请注意,在以下情况下,结果会有所不同(10 10 10):

      static final int alex1 = 10;
      

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

      【讨论】:

      • +1 用于编译时常量表达式。 (为什么还要Integer.parseInt?)
      【解决方案4】:

      静态字段没有什么特别之处,只是编译器无法验证您使用的方法可以在初始化之前访问字段。

      例如

      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
      

      【讨论】:

        【解决方案5】:

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

        为了更好的解释阅读http://download.oracle.com/javase/tutorial/java/javaOO/classvars.html

        【讨论】:

        • 是的,但最终字段始终必须在编译或运行时在对象创建之前进行初始化。您所说的适用于普通字段而不是最终字段。ps 它有效,我在打印之前说过0 10 10
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-06
        相关资源
        最近更新 更多