【问题标题】:Value of static variable not changed even after initializing the child class in Java [duplicate]即使在Java中初始化子类后静态变量的值也没有改变[重复]
【发布时间】:2019-04-07 21:40:26
【问题描述】:

当我通过使用Checks.yChecks 是一个子类)调用静态变量y 时,不会执行静态块并且不会更新y 的值。

class Par {
    static int y = 4;
}

class Checks extends Par {
    static {
        y = 5;
    }
}

public class Check {
    public static void main(String args[]) {
        System.out.println(Checks.y); // here printing 4
    }
}

由于所有子类之间共享静态,因此应该更新该值。

这背后的原因是什么?

【问题讨论】:

    标签: java inheritance jvm static-variables static-block


    【解决方案1】:

    y 字段未由类 Checks 声明。

    读取静态字段不会触发引用类 (Checks) 的初始化,除非该类是声明该字段的类(请参阅下面的 JLS 引用)。在这个例子中,即使通过Checks访问y,也只会触发Par的初始化,因为Par是声明y的类。

    换句话说,Checks 类在某种意义上是在运行时不使用的。

    这也许可以说明为什么通过子类访问 static 成员是错误的,这会导致编译时警告。


    the specification中有一个简单的解释:

    12.4.1。初始化发生时

    一个类或接口类型 T 将是 在任何一个第一次出现之前立即初始化 以下:

    • T 是一个类,并创建了一个 T 的实例。

    • T 声明的静态方法被调用。

    • 分配了一个由 T 声明的静态字段。

    • 使用了 T 声明的静态字段,并且该字段不是常量 变量(第 4.12.4 节)。

    • T 是顶级类(第 7.6 节)和断言语句(第 14.10 节) 执行词法嵌套在 T (第 8.1.3 节)中。
      ...
      对静态字段的引用(第 8.3.1.1 节)只会初始化实际声明它的类或接口,即使它可能通过子类、子接口或实现的类的名称来引用一个界面。

    最后一个注释解释了为什么你的子类没有被初始化。

    【讨论】:

    • 谢谢!我错过了只有在“检查”(子)类本身声明了静态字段时才会发生初始化的观点!如果我在子类中将 y 声明为静态,那么子类的 y 会隐藏父类的(Par)y?
    • 你的意思是检查还是真的检查?
    • @Jean-FrançoisSavard 谢谢,我的意思是Checks。类名在某个时候被更改了,我在编辑时弄错了。
    • @rawat 是的,孩子的字段会隐藏父母的。在子类中重新声明字段名称是另一件坏事,原因与此类似。如果您需要在初始化子类时重新分配字段,那么最好的选择可能是通过子类中的静态方法访问该值
    • @ernest_k 谢谢,我的意思是“Checks”,因为它是“Par”类的子类,顺便说一句..抱歉打错了!
    【解决方案2】:

    这是您如何强制初始化Checks 类的一种变体。

    class Par {
        static int y = 4;
    }
    
    class Checks extends Par {
        public static int x;
        static {
            y = 5;
        }
    }
    
     class Check {
        public static void main(String args[]) {
            System.out.println(checks.y); // Prints 4
            System.out.println(checks.x); // Prints 0
            System.out.println(checks.y); // Prints 5
        }
    }
    

    【讨论】:

      【解决方案3】:

      根据您的示例,Check 类的静态块永远不会被调用。静态块总是在创建对象之前运行。如果您在 Check 类中添加检查 object = new checks(),您应该会看到预期值。

      【讨论】:

      • "检查静态块永远不会被调用。"只是用不同的词来描述有问题的问题。真正的问题是“为什么不在这里执行”。
      • “静态块总是在创建对象之前运行”不清楚,你的意思是每次创建每个对象之前都会发生吗?
      【解决方案4】:

      这里:

      System.out.println(checks.y); // Here printing 4
      

      y 指的是par 类的字段。此字段访问导致根据JLS(重点是我的)加载par 类(父类):

      12.4。类和接口的初始化

      ....

      12.4.1。初始化发生时

      一个类或接口类型 T 将在 以下任何一项的首次出现: T 是一个类,并创建了一个 T 的实例。静态方法 由 T 声明的被调用。

      分配了一个由 T 声明的静态字段。

      使用了 T 声明的静态字段,且该字段不是常量 变量(第 4.12.4 节)。

      T 是顶级类(第 7.6 节)和断言语句(第 14.10 节) 执行词法嵌套在 T(第 8.1.3 节)中。

      但它不会加载 checks 类,因为(重点是我的):

      对静态字段的引用(第 8.3.1.1 节)仅导致初始化 实际声明它的类或接口,即使它可能 通过子类、子接口或 实现接口的类。

      【讨论】:

        【解决方案5】:

        正如其他人提到的,由于类未初始化为 answered before,因此未执行静态块。

        注意类约定名称以大写字母开头。

        没有使用更清晰的示例来显示覆盖类:

        class Par {
            static int y = 4;
            public static void main(String args[]) {
                System.out.println(Checks.y);    // Here printing 4
                System.out.println(new Checks().y);    // Here printing 5
            }
        }
        
        class Checks extends Par {
           static {
                y = 5;
            }
        }
        

        【讨论】:

          【解决方案6】:

          来自JLS 12.4.1

          一个类或接口类型 T 将在 以下任何一项的第一次出现:

          • T 是一个类,并创建了一个 T 的实例。
          • T 是一个类,调用了 T 声明的静态方法。
          • 分配了由 T 声明的静态字段。
          • 使用了由 T 声明的静态字段,并且该字段不是常量变量(第 4.12.4 节)。
          • T 是一个顶级类(第 7.6 节),执行词法嵌套在 T(第 8.1.3 节)中的断言语句(第 14.10 节)。

          由于 y 未在检查中声明,因此上述条件均不满足。

          说明这种行为的另一种方式:

          class par {
              static int y = 4;
              static {
                  System.out.println("static constructor of par");
              }
          }
          
          class checks extends par {
              static int x = 6;
              static {
                  System.out.println("checks static constructor");
                  y = 5;
              }
          }
          
          public class check{
              public static void main(String args[]){
                  System.out.println(checks.y);
                  System.out.println(checks.x);
                  System.out.println(checks.y);
              }
          }
          

          输出

          static constructor of par
          4
          checks static constructor
          6
          5
          

          所以在调用满足第二条规则的checks.x 之后,静态构造函数被调用。

          【讨论】:

            【解决方案7】:

            到目前为止没有提到的一个方面可能会让新的 Java 程序员感到困惑:您如何组织源代码这一事实在某种程度上并不重要!

            您在一个文件或一个示例中拥有两个类(应该命名为 Parent 和 Child btw 以遵循 Java 命名约定)。所以你可能会假设:这些东西在运行时会自动组合在一起。

            但在运行时,每个类都有单独的类文件。正如其他人所说:代码中没有任何内容引用子类。因此没有加载该类,因此不会发生分配!

            【讨论】:

              【解决方案8】:

              这是因为 checks 类中的 static 块没有被执行。尽管您提到了 checks 类,但 JVM 根本不会加载它。

              您可以通过在静态块中添加另一个System.out.println 来确认这一点。

              class checks extends par {
              
                  static {
                      System.out.println("Test");
                      y = 5;
                  }
              }
              

              Test 这个词永远不会被打印出来。


              阅读 Java 语言规范的12.4.1. When Initialization Occurs 部分了解更多信息。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-11-21
                • 2010-12-11
                • 1970-01-01
                • 1970-01-01
                • 2017-08-16
                相关资源
                最近更新 更多