当你执行A thing = new B()时,实例变量调用如何工作; 其中B是A的子类?

这可能在某处得到了回答,但我不知道该搜索什么。 想象一下,你有以下……

超类,Animal.java

public class Animal { public String noise = "squeak"; public String toString() { return noise; } } 

子类,Lion.java

 public class Lion extends Animal { public String noise = "ROAR!!"; public String toString() { return noise; } } 

主要类Runner.java

 public class Runner { public static void main(String[] args) { Animal a = new Animal(); System.out.println(a); System.out.println(a.noise); Lion b = new Lion(); System.out.println(b); System.out.println(b.noise); Animal c = new Lion(); System.out.println(c); System.out.println(c.noise); } } 

输出是:

 squeak squeak ROAR!! ROAR!! ROAR!! squeak 

为什么c.noise会返回吱吱声? 实例方法调用和实例变量之间有什么区别,一个返回你期望的,另一个不返回? 为什么Java会这样做?

谢谢

简短回答:

您可以覆盖方法,但不能覆盖字段。

答案很长:

每个类都会看到它自己的方法和字段以及它的父节点(私有方法除外)。 如果子进程delcares一个名称相同的方法,作为其父类中方法的名称,则此方法将被覆盖 – 如果以某种方式在子实例上调用此方法(即使是从父方法之一),将使用全新的方法而不是父方法。 孩子仍然可以通过super.method(...)调用调用他最后一个父母的原始方法。

但是当我们来到田野时,故事就不同了。 如果子元素声明了一个新字段,它的名称与父类中的字段完全相同,它将简单地隐藏父字段而不覆盖,就像本地变量隐藏全局字段一样。 因此,子方法只会看到孩子的字段,但父母的方法将继续看到父母的字段,而父母的字段将无法通过父类显示 – 这就是你所拥有的。

孩子可以通过((Parent)this).field访问它的父母的字段。

更长的答案:

所以你真正做到这一点的方式就是定义狮子:

 public class Lion extends Animal { public Lion() { noise = "ROAR!!"; } } 

所以现在对于Lion实例,Animal的噪声成员变量已经更新为ROAR !!

当然,你(几乎)从来没有真正拥有类似野外的类的公共可变成员。

你不能覆盖字段, Lion隐藏父noise属性中的新noise声明。 这样做:

 public class Lion extends Animal { // public String noise = "ROAR!!"; // <---- Remove this line public Lion() { noise = "ROAR"; } public String toString() { return noise; } } 

默认情况下,java中的所有非静态方法都是“虚函数” 。 除非它们被标记为final(这使得该方法不可覆盖)。 Java使用虚方法表来调用正确的对象的方法。

这是因为Java只讨论了方法覆盖。 成员变量只能在子类中隐藏。 所以当你说c.noise它实际上引用父类中的字符串变量作为c如果引用类型为Animal。

您感兴趣的主题是动态调度和虚拟方法表 。 基本上,通过设计,Java允许覆盖方法(假设它们是非final的),并且在运行时JVM将执行适当的实现。 这种多态属性仅提供给方法。 良好的OO设计将决定要封装的字段。