Java继承之谜

时间:2012-12-07 20:45:20

标签: java inheritance

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

Animal.java

public class Animal {
    private String sound;

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

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

Tiger.java

public class Tiger extends Animal {
    public String sound;

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

Jungle.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

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

6 个答案:

答案 0 :(得分:9)

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

 diego.roar();

roar()中调用Animal方法并从sound打印Animal

diego.sound = "Hust hust";

Tigersound变量

中设置声音值
diego.roar();

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

diego.setSound( “BLA”);

Animal声音设为bla

diego.roar();

打印bla,因为带有bla的Animal类的setSound更新声音变量。

的System.out.println(diego.sound);

打印Hust hust,因为diego的类型为Tiger,并且您已访问Tiger的场声并且字段不是多态的。

请参阅java language specification 8.3了解详情。

答案 1 :(得分:2)

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

public String sound;

中删除行Tiger.java

和其中之一:

  • String sound中声明protectedpublicAnimal.java,或
  • setSound()定义Animal.java函数,以便对成员变量进行受控访问(即sound

有关更全面的解释,请参阅Jon Skeet's excellent answer to an almost identical problem yesterday

答案 2 :(得分: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字段的每次访问,您都可以在此时告知隐式thisdiego的静态类型。然后你也知道实际访问了哪两个字段。

答案 3 :(得分:1)

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

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

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

请参阅What you can do in a subclass中的Inheritance Turorial部分。

答案 4 :(得分:0)

Tiger课程更改为:

public class Tiger extends Animal {

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

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

来自sound的{​​{1}}对Animal类不可见,因为它是私有的。因此,您为Tiger子类声明了一个新的sound字段,但这并未覆盖Tiger中的原始字段。 Animal类仍然使用它自己的Animal版本,因为它是它看到的唯一版本。 与方法不同,字段无法覆盖

一种解决方案是使用在基类(sound)中声明的getter / setter方法来对属性进行所有访问,即使是从子类中也是如此。


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

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

Animal

即使在Animal中没有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()。< / p>

当然,这种方法会让你无法改变现有动物物体的声音(没有固定器),使动物不可变,一开始可能看起来不方便,但如果你想一想有一段时间,您可能会发现,在许多情况下,您实际上并不需要以这种方式更改对象的状态。

使用 immutable 对象实际上很方便,因为代码更简单,更安全,因为您不必考虑在程序执行期间可能发生的所有可能状态。

答案 5 :(得分:0)

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