8

我为 Java 中的继承创建了以下难题:

动物.java

public class Animal {
    private String sound;

    public void roar() {
        System.out.println(sound);
    }

    public void setSound(String sound) {
        this.sound = sound;
    }
}  

老虎.java

public class Tiger extends Animal {
    public String sound;

    public Tiger() {
        sound = "ROAR";
    }
}

丛林.java

public class Jungle {
    public static void main(String[] args) {
        Tiger diego = new Tiger();

        diego.roar();
        diego.sound = "Hust hust";
        diego.roar();
        diego.setSound("bla");
        diego.roar();
        System.out.println(diego.sound);
    }
}

输出:

null
null
bla
Hust hust

我想这种奇怪的行为正在发生,因为sound在 Animal 中是私有的,而sound在 Tiger 中是公共的。但是你能解释一下(并告诉我 JLS 的相关部分)为什么会发生这种情况吗?

4

6 回答 6

10

字段不是多态的,方法是多态的。

 diego.roar();

调用roar()方法 in并从Animal打印。soundAnimal

diego.sound = "Hust hust";

Tiger在类sound变量中设置声音值

diego.roar();

返回空值;因为从 Animal 打印声音,它仍然是空的。上面的声音分配反映在 Tiger 类变量上,而不是 Animal 类。

diego.setSound("bla");

Animal声音设置为bla

diego.roar();

打印bla是因为 setSound 更新了 Animal 类的声音变量bla

System.out.println(diego.sound);

打印Hust hust是因为 diego 是 typeTiger并且您访问了 field sound ofTiger并且字段不是多态的。

有关详细信息,请参阅java 语言规范 8.3 。

于 2012-12-07T20:49:51.470 回答
2

您可以覆盖 Java 中的函数,而不是变量。

public String sound;从_Tiger.java

或者:

  • 声明String soundprotectedpublic,Animal.java
  • 为受控访问成员变量定义一个setSound()函数(即)Animal.javasound

有关更全面的解释,请参阅Jon Skeet 昨天对几乎相同问题的出色回答

于 2012-12-07T20:49:04.927 回答
2

正如其他人已经指出的那样:字段不受多态性的影响。

我对此的新转变是:对字段的访问是在编译时静态决定的,而不是在运行时动态决定的。所以在这里

Tiger diego = new Tiger();
diego.sound = "Hust hust";

该变量diego具有静态类型Tiger。因此编译器将生成对Tiger.sound. 但相比之下(如果Animal.sound不会private):

Animal diego = new Tiger();
diego.sound = "Hust hust";

编译器将生成对Animal.sound. 这也可以通过强制转换:

Tiger diego = new Tiger();
((Animal)diego).sound = "Hust hust";

考虑到这一点,您可以完成您的难题,并且对于对任何sound字段的每次访问,您都可以告诉静态类型是隐式的this还是diego那个点的静态类型。然后您还知道实际访问了这两个字段中的哪一个。

于 2012-12-07T21:04:47.880 回答
1

您必须认识到Animal.sound与 不同的字段Tiger.sound。实际上,您有两个不同的字段,可以有两个不同的值,并以两种不同的方式设置。

Animal.setSound()更新 的值Animal.sound,不更新 的值Tiger.sound

diego.sound = "Hust hust"更新 的值Tiger.sound,而不是 的值Animal.sound

请参阅Inheritance Turorial中您可以在子类中做什么部分。

于 2012-12-07T20:58:32.560 回答
0

将类更改Tiger为:

public class Tiger extends Animal {

    public Tiger() {
        setSound("ROAR");
    }
}

问题是roar()定义的方法使用了在同一个类中定义Animal的成员私有字段。soundAnimal

soundfrom对班级Animal不可见Tiger,因为它是私有的。因此,您为子类声明了一个新sound字段Tiger,但这并没有覆盖来自Animal. 该类Animal仍然使用它自己的版本sound,因为它是它看到的唯一版本。与方法不同,字段不能被覆盖

一种解决方案是使用基类 ( Animal) 中声明的 getter/setter 方法对属性的所有访问,甚至从子类中访问。


另一种可能的解决方案是使用抽象方法和多态性:

您无需在 Animal 基类中实现 sound 方法,您只需声明一个抽象方法并强制子类提供它们自己的实现:

public abstract class Animal {
    public void roar() {
        System.out.println(sound());
    }

    public abstract String sound();
}

public class Tiger extends Animal {
    public String sound() {
        return "ROAR";
    }
}

public class Dog extends Animal {
    public String sound() {
        return "HOOF HOOF";
    }
}

即使sound()Animal 中的方法没有实现(没有带有代码的主体),仍然可以从该类的其他方法调用该方法,例如roar().

当然,这种做法会让你无法改变一个已经存在的动物对象的声音(没有setter),让动物不可变,乍一看可能看起来很不方便,但如果你仔细想想,你可能会发现在许多情况下,您实际上不需要以这种方式更改对象的状态。

使用不可变对象实际上很方便,因为代码更加简单和安全,因为您不必考虑程序执行过程中可能出现的所有可能状态。

于 2012-12-07T20:50:01.690 回答
0

system out 语句 (1) 和 (2) 指的是超类的实例变量声音,它没有被继承,因为实例/类变量在 java 中没有被继承,并且您没有设置超变量。(3), super 变量是通过调用继承的方法来设置的。(4) 是在你进行直接赋值时设置的。

于 2012-12-07T20:50:18.737 回答