面向对象编程中,某一个实例方法使用实例变量和调用其它实例方法的情况是常见的。当存在继承关系时,这种情况就变得复杂起来。以下就对继承关系中,父类的某实例方法使用实例变量和其它实例方法的情况进行探究。因为我也是初学者,有理解不到位的地方,还请路过的朋友多多指教。
(1)父类实例方法使用实例变量
public或protected修饰的实例变量。
因为在继承关系中,public和protected修饰的实例变量对于子类的效果是相同的,所以在此合并讨论。
public class Test { public static void main(String[] args) { System.out.println(new Parent().getName()); System.out.println(new Child().getName()); }}class Parent { public String name = "base"; public String getName() { return name; }}class Child extends Parent { public String name = "child"; }
输出结果:
basebase
由此可见,父类的方法如果使用了父类的实例变量,即使在子类中重新定义了一个和该变量名称相同的实例变量,父类的方法在通过子类调用的时候,依然使用的是父类的变量。
了解这一点后,我们可以做一个有趣的实验。下面这段代码会输出什么内容?
public class Test { public static void main(String[] args) { Child c = new Child(); c.name = "Child"; System.out.println(c.name); //(1)这里输出的是子类的name System.out.println(c.getName()); //(2)这里输出的时父类的name }}class Parent { public String name = "base"; public String getName() { return name; }}class Child extends Parent { }
(1)处的代码输出的name,是通过子类引用找到的name,而(2)出代码调用的是继承自父类的getName()方法,根据之前测试的结果,该方法访问的肯定是父类的name。这里问题的关键在于,子类引用找到的name和父类的name是不是同一个name呢?运行的结果显示:是同一个!
输出结果
ChildChild
有这里可以看出,子类继承父类后,子类自身并未多出一个name来,其使用的name仍然是父类的name。也就是说,在内存结构中,子类继承自父类的实例变量,仍然是父类的实例变量,只是对于子类而言是可见的并且表现为子类自身实例变量。
从上述例子还可以看到,子类在声明一个同名成员变量后,子类使用的便是自身的成员变量了。其实此时父类的同名成员变量对子类仍然可见,并且子类可以通过super.变量名来使用该父类的成员变量。
private修饰的成员变量。
其实这时已经不用进一步讨论了,根据上边的结论,父类方法不会访问子类的public/protected员变量,那么肯定不会也不会访问到private修饰的成员变量。
综上所述,子类继承自父类的实例方法可以访问的实例变量是父类的实例变量,对于子类自身的实例变量是不能访问的!如果在继承的时候,子类定义了一个和继承自父类的实例变量名称相同的实例变量,则会导致子类不能直接访问父类,进而导致子类方法和父类方法对同名实例变量的操作不一致。所以,不建议子类定义和继承自父类实例变量名称相同的实例变量!
(2)父类实例方法调用实例方法
同样地,分public/protected和private两种情况进行讨论。
public/protected修饰的实例方法。
测试的方法很简单,只需要在对上面的例子稍微改动一下即可。
public class Test { public static void main(String[] args) { new Parent().print(); new Child().print(); }}class Parent { private String name = "base"; public String getName() { return name; } public void print() { System.out.println(getName()); }}class Child extends Parent { private String name = "child"; public String getName() { return name; }}
输出结果:
basechild
很明显,子类的getName()覆写了父类的getName(),最终导致两个getName()访问的变量不一致。这里值得注意的是,父类实例方法调用另一个(子类可见的)实例方法,并且后者被子类重写时,是存在多态的。而且可进一步验证,即使print()方法的修饰符改为private,多态依然存在。
private修饰的实例方法
当方法有private修饰时,多态就不存在了。代码如下
public class Test { public static void main(String[] args) { new Parent().print(); new Child().print(); }}class Parent { private String name = "base"; private String getName() { return name; } public void print() { System.out.println(getName()); }}class Child extends Parent { private String name = "child"; private String getName() { return name; }}
输出结果
basebase
这也是可以理解的,因为此时不存在方法的重写,所以也不存在多态。
总结一下:
父类方法访问实例变量只能是父类的实例变量,子类继承父类时。从内存结构上考虑,可以认为子类实例所占据的内存结构中内部有一份完整的父类实例内存结构。父类实例中public/protected修饰的实例变量对于子类实例而言是可见的,并且可以如同访问自身实例变量一样去访问,当然前提是子类自身实例变量中没有父类可见实例变量重名的。另一方面,父类实例中private修饰的实例变量对于子类而言不可见。
而当父类方法调用实例方法时,是可能存在多态的。具体说来,如果父类实例方法调用的实例方法被子类重写,当通过子类引用调用前者时,前者所调用的实例方法将采用被子类重写后的那个。然而,如果子类方法和父类方法并无重写关系(private修饰),那么这种多态效应将不存在,通过子类调用前者时,前者依然使用父类的方法。