我在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的相关部分)为什么会这样?
答案 0 :(得分:9)
字段不是多态的,方法是多态的。
diego.roar();
在roar()
中调用Animal
方法并从sound
打印Animal
。
diego.sound = "Hust hust";
在Tiger
类sound
变量
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
中声明protected
为public
或Animal.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
字段的每次访问,您都可以在此时告知隐式this
或diego
的静态类型。然后你也知道实际访问了哪两个字段。
答案 3 :(得分:1)
您必须认识到Animal.sound
与Tiger.sound
不是同一个字段。实际上,您有两个不同的字段,可以有两个不同的值,并以两种不同的方式设置。
Animal.setSound()
更新Animal.sound
的值,不会更新Tiger.sound
的值。
diego.sound = "Hust hust"
更新Tiger.sound
的值,而不是Animal.sound
的值。
答案 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)在进行直接赋值时设置。