【问题标题】:why static block cannot access the static variable defined after it为什么静态块不能访问它之后定义的静态变量
【发布时间】:2019-08-09 15:06:33
【问题描述】:

我检查了Forward References During Field Initialization 和这个the answer from @assylias,但我仍然没有得到为什么的答案。

为什么静态块可以分配在它之后声明的静态变量,但可以不能访问它?

   class Parent {
        static {
            i = 2; // valid
            // can only assign new value to it instead of accessing it?
//            System.out.println(i); // invalid - compile-error
        }
        static int i = 0;
        static {
            i = 3; // valid
        }
    }

是不是因为:这个值还没有初始化,所以我们只是明确地禁止你使用它?还是我不知道与安全有关的事情?


更新

这不是那个问题的重复

为什么使用类名访问时不会发生这种情况?

这个问题是关于为什么我们有这个设计?目的是什么?

【问题讨论】:

  • 会有compile error 提到@Lino 所以你根本无法获取字节码
  • @Lino 此代码无法编译。没有要检查的字节码。不要在这里发布无意义的猜测。
  • 是的,是因为它还没有初始化,错误信息是这样说的,不是吗?
  • @Hearen 询问为什么语言的决定是不容易回答的问题。这需要一位 java 开发人员的深入知识,除非 Brian Goetz 出现,否则我不确定这里是否有人可以回答这个问题

标签: java java-8 static static-block


【解决方案1】:

静态字段根据它们在代码中出现的顺序进行初始化

因此,当您为i 变量赋值时,您只需对编译器说:“嘿,伙计,当您开始初始化此变量时,请将其值设置为...”。但是在初始化之前您不能使用它,因为它根本不存在。

更新:

正如 James Gosling、Bill Joy、Guy Steele 和 Gilad Bracha 在《Java 语言规范》一书中所说:

这些限制旨在在编译时捕获循环或 否则格式错误的初始化。

考虑一下:

static {
            i = 2;
            j = i + 5; //should it be 7 or 15?
}
static int i = 10;
static int j;

j 变量应该是 7 还是 15? 如果是 7,那么我们已经初始化了两次 i 变量,这是不可能的,因为该字段是静态的。如果是 15,那么i = 2; 是什么意思?

此代码不明确,因此 Java 规范不允许这样做。

【讨论】:

  • 提供了一些解释。
  • 哇,非常感谢。我只是迷路了,不知道为什么不允许这样做。这个例子确实给了我线索。太感谢了!帕维尔
  • 那么我们已经初始化了两次i变量,这是不可能的,因为该字段是静态的” 咳咳,你为什么认为这是不可能的?您可以随意分配变量,除非它是 final。您可以轻松编写 static int i = 2, j = i++ + 5; static { i += 3; } 或者您将自己示例中的 j = i + 5; 更改为 j = ClassName.i + 5; 并且它可以工作,因为变量 确实 已经存在。
  • 它在 Java 规范中是这样说的:“在运行时,初始化器被评估并且赋值只执行一次,当类被初始化时”。这里docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.3.3 和这里docs.oracle.com/javase/specs/jls/se8/html/… 关于通过全名引用变量。在这种情况下,编译器确切地知道该类已经被初始化这个问题在这里讨论过:stackoverflow.com/questions/29153703/…
【解决方案2】:

经过进一步阅读,我认为 Pavel 在这一点上并不十分准确,正如 @Holger 在评论中指出的那样。

我们已经初始化 i 变量两次,这是不可能的,因为该字段是静态的。

正如12.4.2. Detailed Initialization Procedure 指出的那样

对于每个类或接口C,都有一个唯一的初始化锁LC。从 C 到 LC 的映射由 Java 虚拟机实现自行决定。

我认为初始化两次对类初始化器本身来说是可以的,只要它对调用客户端来说只是一次

但是 Pavel 提供的演示仍然站得住脚,所以我基本上只是在这里重用它,但有不同的解释。

static {
       i = 2;
       j = i + 5; 
       // no one knows whether "i" here initialized properly here
}
static int i = 10;
static int j;

但是当你直接在j = MyClass.i + 5 中使用MyClass.i 时,编译器会知道这样就可以了,因为8.3.3. Forward References During Field Initialization 详细说明了四个条件。

具体来说,如果以下所有都是编译时错误 真的:

  1. 在类或接口C中出现类变量的声明 使用类变量后的文本;

  2. 在 C 的类变量初始化器中使用一个简单的名称 或 C 的静态初始化程序;

  3. 用法不在作业的左侧;

  4. C 是包含使用的最内层类或接口。

answer 中已经有详细的讨论。

为了结束,我认为这将是可预测的行为添加这些限制。再次,8.3.3. Forward References During Field Initialization 中指出的另一个官方目的。

这些限制旨在在编译时捕获循环或其他格式错误的初始化。

【讨论】:

    【解决方案3】:

    同样适用于非静态成员。您可以在实例初始化程序块中为它们赋值,但在初始化之前不能使用它们。

    class Parent {
        {
            x = 3; // works fine
            // System.out.println(x); // gives compilation error.
    
        }
        int x = 0;
    
        public static void main(String[] args) {
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-01
      • 2011-12-08
      • 1970-01-01
      • 2015-07-22
      • 2012-06-29
      • 1970-01-01
      • 1970-01-01
      • 2011-01-18
      相关资源
      最近更新 更多