有没有一种方法可以在Java中覆盖类变量?

174
class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";
}

public void doIt()
{
    new Son().printMe();
}

函数doIt将会输出字符串"dad"。是否有一种方法使它输出字符串"son"?


123
如果你看到一个被保护的静态变量,就赶紧跑。 - Tom Hawtin - tackline
25
@TomHawtin-tackline,对不起我的无知,但为什么受保护的静态成员会被看作是不好的?我试着用谷歌搜索,但没有找到明确的答案。谢谢。 - Tony Chan
18
请查看这个问题:[为什么我们不应该在Java中使用protected static] (https://dev59.com/_mAf5IYBdhLWcg3wyVB1)。 - Zeeshan
17个回答

105
简而言之,不,没有办法覆盖一个类变量。
在Java中,你不能覆盖类变量,只能隐藏它们。"覆盖"是针对实例方法的,而"隐藏"则与覆盖不同。
在你所提供的示例中,通过在Son类中声明名为"me"的类变量,你隐藏了它从其超类Dad继承的同名"me"类变量。这种方式隐藏变量并不会影响超类Dad中类变量"me"的值。
至于你问题的第二部分,如何使它打印"son",我会通过构造函数设置变量的值。虽然下面的代码与你原来的问题有点不同,但我会写成这样:
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

JLS在第8.3节-字段声明中对隐藏提供了更多详细信息。


1
在派生类中有一个静态块,可以实现覆盖。 - siddagrl
1
@siddagrl: 你能详细解释一下吗? - Mr_and_Mrs_D
在这个例子中,OP的类中Person应该替换哪个类? - n611x007
1
@naxa SonDad应该继承自Person,然后在它们的构造函数中调用super("Son or Dad"); - Panzercrisis
在Java中隐藏变量是好还是坏的行为? - Panzercrisis

75

是的。但就变量而言,它被覆写(给变量赋新值。给函数重新定义是覆盖)。只需在构造函数或静态块中不声明变量,而是进行初始化(更改)即可。

当在父类的块中使用时,该值将得到反映。

如果变量是静态的,则在初始化时使用静态块来修改变量值。

class Son extends Dad {
    static { 
       me = "son"; 
    }
}

否则在构造函数中进行更改。

您还可以在任何区块中稍后更改该值。它将反映在超类中。


10
我认为答案应该加入一些代码以提高可读性。只需将Son实现更改为class Son extends Dad { static { me = 'son' } }即可。 - Cléssio Mendes

31

是的,只需覆盖 printMe() 方法:

class Son extends Dad {
        public static final String me = "son";

        @Override
        public void printMe() {
                System.out.println(me);
        }
}

如果您正在使用的库没有 printMe() 方法,或者即使它有该方法,它也是一个静态方法,那该怎么办? - Sudheer Aedama
3
为了更贴近实际情况,你可以将"printMe"方法视为一个非常复杂的函数,其中包含你不想重复的逻辑。在这个例子中,你只想改变人名,而不是打印人名的逻辑。你应该创建一个名为"getName"的方法,你可以重写该方法返回"son",并且"printMe"方法会调用getName方法来打印名字。 - Ring

17

你可以创建一个 getter 然后重写它。这在变量的子类是自身的情况下特别有用。想象一下,你的超类有一个 Object 成员,但在子类中,它现在更明确定义为一个 Integer

class Dad
{
        private static final String me = "dad";

        protected String getMe() {
            return me;
        }

        public void printMe()
        {
                System.out.println(getMe());
        }
}

class Son extends Dad
{
        private static final String me = "son";

        @Override
        protected String getMe() {
            return me;
        }
}

public void doIt()
{
        new Son().printMe(); //Prints "son"
}

12
如果你要覆盖它,我看不出保持这个静态的有效理由。我建议使用抽象(请参见示例代码)。:
如果您打算覆盖它,我认为没有必要保持它的静态性。我建议使用抽象化(请参考示例代码)。
     public interface Person {
        public abstract String getName();
       //this will be different for each person, so no need to make it concrete
        public abstract void setName(String name);
    }

现在我们可以添加父亲:

public class Dad implements Person {

    private String name;

    public Dad(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
    return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

儿子:

public class Son implements Person {

    private String name;

    public Son(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

然后爸爸遇到了一位很好的女士:

public class StepMom implements Person {

    private String name;

    public StepMom(String name) {
        setName(name);
    }

    @Override
    public final String getName() {
        return name;
    }

    @Override
    public final void setName(String name) {
        this.name = name;
    }
}

看起来我们有一个家庭,让我们告诉世界他们的名字:

public class ConsoleGUI {

    public static void main(String[] args) {
        List<Person> family = new ArrayList<Person>();
        family.add(new Son("Tommy"));
        family.add(new StepMom("Nancy"));
        family.add(new Dad("Dad"));
        for (Person person : family) {
            //using the getName vs printName lets the caller, in this case the
            //ConsoleGUI determine versus being forced to output through the console. 
            System.out.print(person.getName() + " ");
            System.err.print(person.getName() + " ");
            JOptionPane.showMessageDialog(null, person.getName());
    }
}

}


1
该帖子涉及更改静态访问权限。因此,“me”是一个通用的“dad”或“son” - 不需要为每个父亲或儿子创建,因此是静态的。 - RichieHH
是的,你说得对,我没有看到它是静态的。哈哈,这会让我的答案少大约100行代码,如果有答案的话。谢谢提醒。 - nckbrz

6
这似乎是一个设计缺陷。将静态关键字移除,并在构造函数中设置变量,这样Son只需在其构造函数中将变量设置为不同的值即可。

1
这个有什么问题吗?如果在你的设计中'me'成员变量应该是可重写的,那么Patrick的解决方案是正确的。 - Chii
实际上,如果每个实例的me值都相同,只需删除“static”即可。初始化不必在构造函数中进行。 - Nate Parsons
没错,虽然从技术上讲(在字节码中),我认为它们几乎是一样的;-) - Patrick Cornelissen

6

虽然类变量只能在子类中隐藏而不是重写,但仍然可以通过反射实现不需要在子类中重写printMe()的功能。在下面的代码中,出于清晰起见,我省略了异常处理。请注意,在此情况下将me声明为protected似乎没有太多意义,因为它将被子类隐藏...

class Dad
  {
    static String me = "dad";

    public void printMe ()
      {
        java.lang.reflect.Field field = this.getClass ().getDeclaredField ("me");
        System.out.println (field.get (null));
      }
  }

非常有趣,Java.lang.reflect 呀?... +1 - nckbrz
要注意的是,如果你将me变量声明为protected,那么在使用它之前,你还需要启用对该变量的访问权限,因为父类无法访问子类的protected字段。 - Rik Schaaf

5

https://docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html

这篇文章讲述了隐藏字段的概念。

来自上述链接

在一个类中,如果一个字段与父类中的字段同名,则会隐藏父类的字段,即使它们的类型不同。在子类中,不能通过简单名称引用父类中的字段。相反,必须通过super进行访问,这将在下一节中介绍。一般来说,我们不建议隐藏字段,因为它会使代码难以阅读。


1
欢迎提供潜在解决方案的链接,但请添加链接周围的上下文,以便其他用户了解它是什么以及为什么存在。在引用重要链接时,请始终引用最相关的部分,以防目标站点无法访问或永久离线。请注意,仅仅是一个外部网站链接可能是为什么和如何删除某些答案?的原因之一。 - FelixSFD

3

这段代码输出的确实是“dad”,因为该字段没有被覆盖,而是被隐藏了。有三种方法可以使其输出“son”:

方法一:重写printMe函数

class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    protected static String me = "son";

    @override
    public void printMe()
    {
        System.out.println(me);
    }
}

public void doIt()
{
    new Son().printMe();
}

方法二:不要隐藏字段,在构造函数中初始化
class Dad
{
    protected static String me = "dad";

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    public Son()
    {
        me = "son";
    }
}

public void doIt()
{
    new Son().printMe();
}

方法三:使用静态值在构造函数中初始化字段

class Dad
{
    private static String meInit = "Dad";

    protected String me;

    public Dad() 
    {
       me = meInit;
    }

    public void printMe()
    {
        System.out.println(me);
    }
}

class Son extends Dad
{
    private static String meInit = "son";

    public Son()
    {
        me = meInit;
    }

}

public void doIt()
{
    new Son().printMe();
}

3
变量不参与覆盖。只有方法才会。方法调用在运行时解决,也就是说,调用方法的决定在运行时进行,但变量仅在编译时确定。因此,调用哪个变量取决于调用的引用,而不是运行时对象。
看一下以下代码片段:
package com.demo;

class Bike {
  int max_speed = 90;
  public void disp_speed() {
    System.out.println("Inside bike");
 }
}

public class Honda_bikes extends Bike {
  int max_speed = 150;
  public void disp_speed() {
    System.out.println("Inside Honda");
}

public static void main(String[] args) {
    Honda_bikes obj1 = new Honda_bikes();
    Bike obj2 = new Honda_bikes();
    Bike obj3 = new Bike();

    obj1.disp_speed();
    obj2.disp_speed();
    obj3.disp_speed();

    System.out.println("Max_Speed = " + obj1.max_speed);
    System.out.println("Max_Speed = " + obj2.max_speed);
    System.out.println("Max_Speed = " + obj3.max_speed);
  }

}

当您运行代码时,控制台将显示:
Inside Honda
Inside Honda
Inside bike

Max_Speed = 150
Max_Speed = 90
Max_Speed = 90

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