继承是OOP语言的必备功能,其功能强大,并且又是OO另一个重量级功能——多态——的基础,所以理解继承包括开发平台如何具体实现继承的机制至关重要。
对于java语言的继承机制本来没有想太多,因为教科书或语言规范都写的很清楚了,大概的:base class中的public和protected成员会被derived class自动接收,成为其成员。而base class中的private和包访问(默认访问级别)成员则不会被继承。你可以在derived class中直接使用继承到的成员或者覆写(override)它。
上面这句话理解起来不困难,但很多教科书(反正我一本都没看到)没有谈到在derived class中对继承的成员的两种不同操作——直接使用和覆写后再使用——jdk是如何实现的。不要小看这个问题,他很重要!
在《Think in Java,2nd edition》(侯捷中文版)的第六章有这么一个程序,源代码如下:
//: c06:Detergent.java
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() { append(" foam()"); }
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
x.print();
System.out.println("Testing base class:");
Cleanser.main(args);
}
} ///:~
这里面,Cleanser类中有一个private实例变量s,五个普通public方法,和一个main()方法。根据规范,除了private实例变量s外,五个普通方法和那个mian()方法都会被Detergent类继承。再来看Detergent类,Detergent类中新添了一个方法foam(),另外有两个继承的方法被覆写了,一个是scrub()方法,还有一个,千万要注意,继承自Cleanser类的main()方法也被覆写了!不信你可以试验下,注释掉Detergent类中的mian()方法,编译后用java Detergent执行,照样可以,说明main()方法被继承下来了,不过我又用自己的main()方法覆写了它,这样我就可以执行自己的代码了。
仔细观察上述代码,不要先用jdk执行它,而是在脑子里想它的执行过程,你会发现,当我们在Detergent的实例中调用直接继承的方法,如dilute(), apply()时,他们要调用append()方法,OK没问题,append()方法也是public的,已经被继承了,调用自己的方法这很天经地义,思考上没什么障碍,但,问题出现了,append()方法的实现是要使用String s,而s是Cleancer的private成员,按规定它是不能被继承的,也就是说在Detergent实例里面根本就没有s这个变量!
可是,程序是可以编译执行的,没有任何问题。那么问题当然出在我们自己,是我们没有彻底理解jdk对java继承的实现机制!
经过反复琢磨,反复修改,写测试代码,总算对这个问题有了个比较深入的了解,请大家先看我的结论:jdk把derived class编译成字节代码时,对直接继承和覆写实现的成员,操作是不同的!对于直接继承的成员,并不把其代码加到derived class的代码中,但会保留一个引用(我不知道,我自己的理解和设想),用来指向base class中相应的成员;而对于被覆写过的成员来说,则象对待derived class中的其他自有成员一样,在分配空间是给他们分配自己的空间,也就是说,在编译后的字节代码中会有这些成员的代码。
所以,上面的问题就可以解决了。先看看上面的程序执行后的结果:
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
注意,一定要这样执行:java Detergent,因为Cleanser也有main()方法,你用java Cleanser,也是能执行的,但那只能执行Cleanser类了。
这个程序在进行一系列动作之后分别打印了两次String s的值。第一次是用Detergent类的实例的print()方法打印的,第二次是用Cleanser类的实例的print()方法打印的。对于打印的对象s来说,她只有一个,属于Cleanser类,那为什么Detergent类的方法也能访问到s呢?根据我上面的结论,因为print()方法是直接继承来的,在Detergent类中没有覆写它(也就是说没有把它明示的写出来),所以,这个方法只保留一份,在Cleanser类中,不过,由于Detergent类是Cleanser类的派生类,所以,在Detergent类中有个引用可以让它访问Cleanser类的print()方法,并且在使用的时候就好像使用自己的方法一样。而执行的时候,其实真正的执行都是在Cleanser类的空间里执行的,只不过执行完后把结果返回到Detergent类中而已!所以,因为真正的执行是在Cleanser类的实例中执行的,那么String s就当然可以被访问到啦,所以,结果中的第一行(在Detergent中打印s的内容)的结果其实是Cleanser中的s内容,至于为什么在Cleanser中打印s却不一样?呵呵,这很简单,因为之前新生成了一个Cleanser实例,不一样的实例,数据当然不一样,不信,你在源代码的最后再添一行:x.print();
结果保证是:
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
对于java语言的继承机制本来没有想太多,因为教科书或语言规范都写的很清楚了,大概的:base class中的public和protected成员会被derived class自动接收,成为其成员。而base class中的private和包访问(默认访问级别)成员则不会被继承。你可以在derived class中直接使用继承到的成员或者覆写(override)它。
上面这句话理解起来不困难,但很多教科书(反正我一本都没看到)没有谈到在derived class中对继承的成员的两种不同操作——直接使用和覆写后再使用——jdk是如何实现的。不要小看这个问题,他很重要!
在《Think in Java,2nd edition》(侯捷中文版)的第六章有这么一个程序,源代码如下:
//: c06:Detergent.java
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() { append(" foam()"); }
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
x.print();
System.out.println("Testing base class:");
Cleanser.main(args);
}
} ///:~
这里面,Cleanser类中有一个private实例变量s,五个普通public方法,和一个main()方法。根据规范,除了private实例变量s外,五个普通方法和那个mian()方法都会被Detergent类继承。再来看Detergent类,Detergent类中新添了一个方法foam(),另外有两个继承的方法被覆写了,一个是scrub()方法,还有一个,千万要注意,继承自Cleanser类的main()方法也被覆写了!不信你可以试验下,注释掉Detergent类中的mian()方法,编译后用java Detergent执行,照样可以,说明main()方法被继承下来了,不过我又用自己的main()方法覆写了它,这样我就可以执行自己的代码了。
仔细观察上述代码,不要先用jdk执行它,而是在脑子里想它的执行过程,你会发现,当我们在Detergent的实例中调用直接继承的方法,如dilute(), apply()时,他们要调用append()方法,OK没问题,append()方法也是public的,已经被继承了,调用自己的方法这很天经地义,思考上没什么障碍,但,问题出现了,append()方法的实现是要使用String s,而s是Cleancer的private成员,按规定它是不能被继承的,也就是说在Detergent实例里面根本就没有s这个变量!
可是,程序是可以编译执行的,没有任何问题。那么问题当然出在我们自己,是我们没有彻底理解jdk对java继承的实现机制!
经过反复琢磨,反复修改,写测试代码,总算对这个问题有了个比较深入的了解,请大家先看我的结论:jdk把derived class编译成字节代码时,对直接继承和覆写实现的成员,操作是不同的!对于直接继承的成员,并不把其代码加到derived class的代码中,但会保留一个引用(我不知道,我自己的理解和设想),用来指向base class中相应的成员;而对于被覆写过的成员来说,则象对待derived class中的其他自有成员一样,在分配空间是给他们分配自己的空间,也就是说,在编译后的字节代码中会有这些成员的代码。
所以,上面的问题就可以解决了。先看看上面的程序执行后的结果:
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
注意,一定要这样执行:java Detergent,因为Cleanser也有main()方法,你用java Cleanser,也是能执行的,但那只能执行Cleanser类了。
这个程序在进行一系列动作之后分别打印了两次String s的值。第一次是用Detergent类的实例的print()方法打印的,第二次是用Cleanser类的实例的print()方法打印的。对于打印的对象s来说,她只有一个,属于Cleanser类,那为什么Detergent类的方法也能访问到s呢?根据我上面的结论,因为print()方法是直接继承来的,在Detergent类中没有覆写它(也就是说没有把它明示的写出来),所以,这个方法只保留一份,在Cleanser类中,不过,由于Detergent类是Cleanser类的派生类,所以,在Detergent类中有个引用可以让它访问Cleanser类的print()方法,并且在使用的时候就好像使用自己的方法一样。而执行的时候,其实真正的执行都是在Cleanser类的空间里执行的,只不过执行完后把结果返回到Detergent类中而已!所以,因为真正的执行是在Cleanser类的实例中执行的,那么String s就当然可以被访问到啦,所以,结果中的第一行(在Detergent中打印s的内容)的结果其实是Cleanser中的s内容,至于为什么在Cleanser中打印s却不一样?呵呵,这很简单,因为之前新生成了一个Cleanser实例,不一样的实例,数据当然不一样,不信,你在源代码的最后再添一行:x.print();
结果保证是:
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
Cleanser dilute() apply() Detergent.scrub() scrub() foam()