《Think in java》里这个例子中为什么父类引用能调用子类对象里的变量?
这里为什么sup.geField()=1?sup.field和sup.geField()结果不是应该一样都是0的吗?
难道是因为sup.geField()在子类中被重写了,所以调用的是子类的变量吗?如果这样的话子类方法和子类变量是在编译期就绑定了么?
回答:
首先看这个:
Super sup = new Sub();
这是sup
变量包含两个方面:
- 它定义的类型是
Super
- 它实际上引用了
Sub
的实例(对象)
定义的类型是给编译器在编译阶段参考的(用于编译时 Debug 和分配好成员变量的地址等);而实际的引用是在运行时通过调用真实引用的对象的方法实现多态的。
所以sup.field
在编译时就确定为Super#field
的地址,所以为0
;
而sup.getField()
在运行时调用Sub#getField()
, 所以是得到Sub#field
的1
.
PS. 你在读这本书时,通常不能指望一次读懂。能看懂多少就看多少,看多几次。不过有个很好的官方教程 The Java Tutorials, 这个教程我觉得是学 Java 最好的资料,就看你习不习惯读英文。
再看一个更加能够说明问题的例子Demo:
class Dad { public int field = 6666;
public int getField() {
return field;
}
}
class Son extends Dad {
public int field = 9999;
public int getField() {
return field;
}
}
public class Main {
public static void main(String[] args) {
Dad dd = new Dad();
Dad ds = new Son();
Son ss = new Son();
// java.lang.ClassCastException: Dad cannot be cast to Son
// Son sd = (Son) new Dad();
// 如果直接访问成员变量(field),
// 它的值由对象引用(dd, ds, ss)的类型决定,
// 而且它的地址在编译期间就已经分配
System.out.println("Dad new Dad(): " + dd.field); // 6666
System.out.println("Dad new Son(): " + ds.field); // 6666
System.out.println("Son new Son(): " + ss.field); // 9999
// 但是如果通过方法间接访问,
// 那么它得到的值由真实的对象决定,与其定义的类型无关
// 而且这是在运行时才能决定的
System.out.println("Dad new Dad(): " + dd.getField()); // 6666
System.out.println("Dad new Son(): " + ds.getField()); // 9999
System.out.println("Son new Son(): " + ss.getField()); // 9999
// 这种不一致性并不是不能避免,反而是为了提高效率而有意为之的
// 继续放大招... 注意同一个对象转型前后发生的变化
// 转型前:9999 转型后:6666
System.out.println("转型前:" + ss.field + " 转型后:" + ((Dad) ss).field);
// 转型前:6666 转型后:9999
System.out.println("转型前:" + ds.field + " 转型后:" + ((Son) ds).field);
System.out.println(ss.field == ((Dad) ss).field); // false
System.out.println(ds.field == ((Son) ds).field); // false
// 这说明了,不要直接访问成员变量!!!!
// 还有一些重要的问题,你慢慢再掌握,:))
}
}
回答:
感谢各位大佬的回答,是我误解了作者的意思。作者的意思应该是这样的:
域(成员变量或称属性)与静态方法不具备多态机制,而这可称之为一种缺陷。
原文:如果你直接访问某个域,这个访问就将在编译期进行解析。
sup.field直接访问了field域,但域不具备多态机制,所以在编译时就确定为Super#field的地址,故sup.field = 0(可以理解为:直接访问属性而未调用方法时,不存在多态机制此时是根据引用类型进行调用,由于引用类型为父类类型,所以只能访问到子类对象里的父类对象的属性) ;
原文:然而,在引用Sub(运行时对象)中的field时所产生的默认域并非Super版本的field域。
getField()为普通方法,而普通方法具备多态机制(多态机制:调用有重写的方法时是根据实际当中new出来的对象调用而不是根据引用类型调用),所以sup.getField()调用了运行时对象Sub中的field域,但这时产生的默认域为Sub版本的field域(在java中,非静态方法是针对每个对象进行调用的,而同一类的每个对象都有不同的成员变量存储空间,而这里调用了运行时Sub类型对象的getField方法故调用的属性是Sub类型对象的属性。(ps:如果方法没有重写而其他部分不变,即子类和父类有同名变量而没有同名方法【未重写,无多态】,由于此时调用的方法为针对子类对象里的父类对象调用的方法,所以调用的属性也会是父类对象的属性,即0。参见文末的测试代码)),故sup.getField() = 1。
原文:尽管这个问题容易令人混淆,但实际上极少发生。
测试代码:
class Super { public int field = 0;
public void getField() {
System.out.println(field);
}
}
class Sub extends Super {
public int field = 1;
// @Override
// public void getField() {
// System.out.println(field+" Override");
// }
//此时输出为0;
//当此部分未注释时,输出为1 Override;
public class TestPolymoph {
public static void main(String args[]) {
Super sup = new Sub();
sup.getField();
}
}
以上是 《Think in java》里这个例子中为什么父类引用能调用子类对象里的变量? 的全部内容, 来源链接: utcz.com/p/175350.html