Java中控制静态变量继承的规则是什么?

50

我有一个类Super:

public class Super {
    public static String foo = "foo";
}

我也有另一个类Sub,它继承了Super

public class Sub extends Super {
    static {
        foo = "bar";
    }

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

当我运行它时,它会打印出 bar
我的第三个(也是最后一个)类是Testing:

public class Testing {
    public static void main (String[] args) {
        System.out.println(Super.foo);
        System.out.println(Sub.foo);
        System.out.println(Super.foo);
    }
}

这将打印:

foo
foo
foo

我不明白为什么从不同的类访问foo会导致其内容不同。有人能解释一下吗?


2
我的意思是,当我从“Testing”访问它时,返回的结果与我从“Sub”访问它时不同。 - jmgrosen
@jmgrosen:啊,现在我明白了。 - T.J. Crowder
3
重要提示:需要区分你所写的内容和如果Sub包含 public static String foo = "bar";时会得到的内容(这时你会得到"foo", "bar", "foo",就如你可能预期的那样)。 - T.J. Crowder
我不确定这段代码的行为是反对拥有既不是私有的也不是最终的静态变量,还是反对让静态初始化程序(即使是隐式的)干扰其他类的静态变量。可能两者都有。 - Ilmari Karonen
你可以用Php来实现!https://www.php.net/manual/en/language.oop5.late-static-bindings.php 他们称之为“后期静态绑定”,Java不支持“final”/“private”/“static”变量的这种方式。 - Thomas Decaux
3个回答

44

我不明白为什么foo的内容会因为你访问它的类而变化。

基本上这是类型初始化的问题。当Sub被初始化时,foo的值被设置为"bar"。然而,在你的Testing类中,对Sub.foo的引用实际上被编译成对Super.foo的引用,所以它最终不会初始化Sub,因此foo永远不会变成"bar"。

如果你将Testing代码改为:

public class Testing {
    public static void main (String[] args) {
        Sub.main(args);
        System.out.println(Super.foo);
        System.out.println(Sub.foo);
        System.out.println(Super.foo);
    }
}
然后它会打印出"bar"四次,因为第一条语句将强制初始化Sub,这将更改foo的值。这完全不是从哪里访问的问题。
请注意,这不仅仅是关于类的加载 - 它涉及类的初始化。类可以被加载而不被初始化。例如:
public class Testing {
    public static void main (String[] args) {
        System.out.println(Super.foo);
        System.out.println(Sub.class);
        System.out.println(Super.foo);
    }
}

这仍然会打印两次 "foo",说明 Sub 没有被初始化 - 但它绝对被 加载了,如果在运行程序之前删除 Sub.class 文件,程序将失败。


2
这看起来是正确的,但您是否知道为什么 Sub.foo 编译成了 Super.foo - jmgrosen
4
是的-因为只有一个变量,由Super声明。正如maerics所说,它可以通过Sub访问,但这并不意味着它是另一个变量。 - Jon Skeet
最终成员也是同样的情况。 - Prashant Shilimkar
2
@PrashantShilimkar:说实话,我不太确定你在问什么……无论如何,最终成员只能被分配一次。 - Jon Skeet
2
基本上这是类型初始化的问题。实际上,这是类加载的问题。当类被加载时,静态块会运行,第一次实例化(或调用静态方法)是导致类被加载的原因。 - Georgian Benetatos
显示剩余7条评论

0

无论您在何处更改静态变量的值,它都是相同的变量foo,在SubSuper中。也不要紧,您创建并修改了多少个new Subnew Super对象。由于Super.fooSub.fooobj.foo共享相同的存储空间,因此将在任何地方看到更改。这也适用于基本类型:

class StaticVariable{
        public static void main(String[] args){
            System.out.println("StaticParent.a = " + StaticParent.a);// a = 2
            System.out.println("StaticChild.a = " + StaticChild.a);// a = 2

            StaticParent sp = new StaticParent();
            System.out.println("StaticParent sp = new StaticParent(); sp.a = " + sp.a);// a = 2

            StaticChild sc = new StaticChild();
            System.out.println(sc.a);// a = 5
            System.out.println(sp.a);// a = 5
            System.out.println(StaticParent.a);// a = 5
            System.out.println(StaticChild.a);// a = 5
            sp.increment();//result would be the same if we use StaticParent.increment(); or StaticChild.increment();
            System.out.println(sp.a);// a = 6
            System.out.println(sc.a);// a = 6
            System.out.println(StaticParent.a);// a = 6
            System.out.println(StaticChild.a);// a = 6
            sc.increment();
            System.out.println(sc.a);// a = 7
            System.out.println(sp.a);// a = 7
            System.out.println(StaticParent.a);// a = 7
            System.out.println(StaticChild.a);// a = 7
        }
}
class StaticParent{
        static int a = 2;
        static void increment(){
            a++;
        }
}
class StaticChild extends StaticParent{
         static { a = 5;}
}

您可以通过对象(例如sc.a)或通过其类名(例如StaticParent.a)引用静态变量/方法。最好使用ClassName.staticVariable来强调变量/方法的静态性,并为编译器提供更好的优化机会。


-3

在Java中,静态成员不会被继承,因为它们是类的属性,并且它们被加载到类区域。它们与对象创建无关。然而,只有子类可以访问其父类的静态成员。


这是正确的(它们并不是“继承”的,而是“共享”的),猜测它被投票下降是因为它没有回答问题。 - chad

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