在Java中,即使初始化子类,静态变量的值仍然没有改变的价值。

32

当我通过使用Checks.y(其中Checks是子类)调用静态变量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
    }
}

由于静态变量在所有子类间共享,其值应该被更新。

这背后可能的原因是什么?

8个回答

29

Checks没有声明字段y

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

换句话说,在运行时实际上未使用类Checks

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


规范中有一个简单的解释:

12.4.1. 初始化时间

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

  • 如果T是类并创建了T的实例。

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

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

  • 使用由T声明的静态字段,且该字段不是常量变量(§4.12.4)。

  • T是顶级类(§7.6),并且在T(§8.1.3)中字面嵌套的assert语句(§14.10)被执行。
    ...
    对静态字段(§8.3.1.1)的引用仅导致实际声明它的类或接口的初始化,即使可以通过子类的名称,子接口或实现接口的类的名称来引用它。

最后一条说明了为什么您的子类未被初始化。


谢谢!我错过了一个重点,即初始化仅在“Check”(子类)类本身声明静态字段时发生!如果我在子类中将y声明为静态变量,那么子类的y会隐藏父类Par的y吗? - tusharRawat
1
你是指检查(Checks)还是只是一个检查(Check)? - Jean-François Savard
1
@Jean-FrançoisSavard 谢谢,我指的是“Checks”。类名在某个时候被更改了,而我在编辑时弄错了。 - ernest_k
@rawat 是的,子类的字段将会隐藏父类的字段。在子类中重新声明字段名称是另一件糟糕的事情,原因和这个类似。如果需要在初始化子类时重新分配该字段的值,那么最好的选择可能是通过子类中的静态方法访问该值。 - ernest_k
@ernest_k 谢谢,实际上我是指“Checks”作为“Par”类的子类,顺便说一下..抱歉打错了! - tusharRawat

9

来自JLS 12.4.1:

一个类或接口类型T将在以下任何一种情况发生之前立即初始化:

  • T是一个类并且创建了T的实例。
  • T是一个类并且调用了T声明的静态方法。
  • 分配了T声明的静态字段。
  • 使用了T声明的静态字段且字段不是常量变量(§4.12.4)。
  • T是一个顶级类(§7.6),并且在T(§8.1.3)内字面嵌套的assert语句(§14.10)被执行。

由于checks中未声明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之后,静态构造函数会被调用。

4
那是因为在checks类中的static块没有被执行。虽然你提到了checks类,但JVM根本没有加载它。
你可以通过在静态块内添加另一个System.out.println来确认这一点。
class checks extends par {

    static {
        System.out.println("Test");
        y = 5;
    }
}

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


阅读Java语言规范的12.4.1. 初始化时机一章以了解更多信息。


4

这里:

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

ypar类的一个字段。根据JLS(重点在于我)此字段访问将导致par类(父类)的加载:

12.4.初始化类和接口

....

12.4.1.当发生初始化时

类或接口类型T将在以下任何一种情况下立即初始化: T是一个类并且创建了T的实例。调用了由T声明的静态方法。 T声明的静态字段被分配。

声明在T中的静态字段被使用且该字段不是常量变量(§4.12.4)。

T是顶级类(§7.6),并执行了嵌套在T中(§8.1.3)的assert语句(§14.10)。

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

对静态字段(§8.3.1.1)的引用仅会导致声明它的类或接口的初始化,尽管可能通过子类、子接口或实现接口的类的名称进行引用。


2
到目前为止还没有提到的一个可能会让新手Java程序员感到困惑的方面是:在一定程度上,你如何组织你的源代码并不重要!您可以将两个类(应该按照Java命名约定命名为Parent和Child)放在一个文件中,或者放在一个示例中。所以您可能会认为:这些东西在运行时自动结合在一起。
但是在运行时,每个类都有单独的类文件。正如其他人所说:代码中没有任何引用子类的内容。因此,该类不会被加载,因此赋值不会发生!

0

正如其他人所提到的,静态块没有被执行,因为该类未被初始化,就像之前回答的那样。

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

一个更清晰的示例来展示未使用覆盖类:

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;
    }
}

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

“Check static block never get called.” 只是用不同的词描述问题。真正的问题是“为什么它在这里没有被执行”。 - Pshemo
“静态块总是在对象创建之前运行”不太清楚,您的意思是每次在创建每个对象之前都会发生吗? - Pshemo

-1
这是一种强制初始化 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
    }
}

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