【重点】变量/方法:java继承里的 隐藏 vs方法
隐藏:父类和子类拥有相同名字的属性或者方法时,父类的同名的属性或者方法形式上不见了,实际是还是存在的。
- 变量隐藏:子类实例变量/静态变量可以隐藏父类的静态/实例变量,总结为“变量”可以交叉隐藏。
- 方法隐藏:只有一种形式,就是父类和子类存在相同的静态方法。
覆盖:子类重写父类的方法,要求方法名和参数类型完全一样(参数不能是子类),返回值和异常比父类小或者相同(即为父类的子类),访问修饰符比父类大或者相同。
- 变量覆盖:不存在
- 方法覆盖:子类实例方法-->[覆盖]-->父类实例方法;子类静态方法-->[覆盖]-->父类静态方法。总结为“方法”不能交叉覆盖。
【重点】定义:隐藏 vs 覆盖
- 被隐藏的属性,在子类被强制转换成父类后,访问的是父类中的属性
- 被覆盖的方法,在子类被强制转换成父类后,调用的还是子类自身的方法
- 因为覆盖是动态绑定,是受RTTI(run time type identification,运行时类型检查)约束的,隐藏不受RTTI约束,总结为RTTI只针对覆盖,不针对隐藏
例子(必看!)
class Parent{ int x=10; public Parent(){ add(2); } void add(int y){ x+=y; } } class Child extends Parent{ int x=9; void add(int y){ x+=y; } public static void main(String[] args){ Parent p=new Child(); System.out.println(p.x); } }
答案
答案应该是10
在上面的试题中,子类Child的实例方法add(int y)覆盖了父类Parent的实例方法add(int y),而子类的实例变量x则是隐藏了父类的实例变量x。
Child对象的初始化过程是:
首先为父类的实例变量x分配内存空间,因为在定义变量x时为它赋了值(int x=10),所以会同时将这个值赋给x。
其次调用父类的无参构造函数,Parent的构造函数中做的唯一的事情就是调用了add(2);
第三、由于子类的add(int y)方法覆盖了父类的方法,所以add(2)实际调用的是子类的方法,在子类的add方法中做了如下操作x+=j;在这里由于子类的实例变量x隐藏了父类 的实例变量x,所以这条语句是针对子类本身的,但是这时还没有为子类的实力变量x分配空间,它的默认值是0,加2之后是2。
第四、父类初始化完毕后接着初始化子类,为子类的x分配内存空间并将它赋值为9,之前的add(2)操作白瞎了。
再次注意Parent p=new Child();这条语句,它是用父类的引用指向子类的对象,而前面已经说过变量只会被隐藏不会被覆盖,所以这时的p.x值应该是父类的10,而不是子类的9;
如果将输出语句换成下面的语句结果就是9了:
System.out..println(((Child)p).x); //首先将p转换成Child类型