Java继承中的静态初始化

17
public class Main {

    public static void main(String[] args) {
        System.out.println(B.x);
    }

}
class A {
    public static String x = "x";
}
class B extends A {
    static {
        System.out.print("Inside B.");
    }
}

问题:为什么输出结果是:x,而不是:Inside B.x

5个回答

11

对于 B.x 的引用会产生以下字节码:

getstatic       #3   <Field int B.x>

根据 Java虚拟机规范

Java虚拟机指令anewarray、checkcast、getfield、 getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、 invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic对运行时常量池进行 符号引用。执行任何这些指令需要 解析其符号引用

因此,JVM应该解析B.x的符号引用。字段解析是按照以下规定进行

为了解决从D到类或接口C中的字段的未解决符号引用,必须首先解析字段引用给出的C的符号引用(§5.4.3.1)。
当解析字段引用时,字段解析首先尝试在C及其超类中查找所引用的字段:
如果C声明了与字段引用指定的名称和描述符相同的字段,则字段查找成功。声明的字段是字段查找的结果。
否则,将对指定类或接口C的直接超级接口递归应用字段查找。
否则,如果C有一个超类S,则对S递归应用字段查找。
否则,字段查找失败。
换句话说,JVM将B.x解析为A.x。这就是为什么只需要加载A类的原因。

好的,但是它的行为在哪里描述,我的意思是在规范中吗? - Taras Koval
这并不是真的。例如,您可以向 B 添加静态字段 x,即使您不重新编译 Main,它也会被打印出来。 - axtavt
@ShyJ:我不喜欢你的第一个答案,但这绝对是现在最好的答案。+1 - durron597

7
因为B.x实际上是A.x,所以只需要加载A类。

4

《Java语言规范》Java SE 7版第12.4节“类和接口的初始化”指定:

类的初始化包括执行其静态初始化程序和类中声明的静态字段(类变量)的初始化程序。

[...]

static字段的引用(§8.3.1.1)导致仅初始化实际声明它的类或接口,即使它可能通过子类、子接口或实现接口的类的名称被引用。

因此,尽管与上面某些答案中的说法相反,为了确定B.xA中声明,必须加载类B,但是只有在执行关于类B的更具体的操作时,类B才会进行初始化(即,其static初始化程序不会真正运行)。


2

只有在直接访问 B 的静态成员时,才需要加载 B。请注意以下代码:

public class TestMain {
    public static void main(String[] args) {
        System.out.println(B.x);
        System.out.println(B.y);
    }

    static class A {
        public static String x = "x";
    }

    static class B extends A {
        public static String y = "y";
        static {
            System.out.print("Inside B.");
        }
    }
}

将输出:

x
Inside B.y

因为只有在访问B中的内容时才需要加载B。这样做可以节省资源。这里是一个关于此主题的好链接。文章中提到:“不要忘记,当JVM加载类时,将执行此代码。JVM将所有这些块组合成一个单独的静态块,然后执行。我想提到几点:”

这看起来像编译器优化,但是编译器可能不同。有没有地方描述它? - Taras Koval
@user1839039:我为您添加了一个链接。 - durron597

2

Class B继承了A类,该类有一个public static variable x,当你调用B.x时就可以访问它。

如果你希望输出Inside B.,你需要创建该类的对象。所有静态代码块都会被执行,或者将静态代码块移到A类中 :-)

JVM加载类时,会将所有静态块分组,并按照声明顺序依次执行。

编辑 (来源):简而言之,Java中的静态成员不会被继承。相反,在派生类的命名空间中直接可见声明的静态成员(受“访问”限制),除非它们被派生类的声明所“隐藏”。

因此,如果静态属于类本身,为什么会传递到派生类?它不应该只留在定义它的类中吗?


是的,但为什么静态块没有被执行。 - Taras Koval
你不需要创建该类的对象来执行静态块。请参考下面的示例。 - durron597
@durron597,我刚刚告诉了一个实现它的方法,无论如何,问题是为什么派生类中的静态代码块没有执行呢 :-) - Amandeep Jiddewar

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