多态笔记
--编程思想第四版
多态是通过分离做什么和**怎么做**,从另一角度将接口和实现分离开来,
多态不仅可以改善代码的组织结构和可读性,还能够创建可扩展的程序--即无论在项目最初创建还是在后面需要添加新功能时,都可以“生长”的程序。
封装:通过合并特征和行为来创建新的数据类型(自定义类)。
多态的作用就是消除类型之间的耦合关系;它允许一种类型表现出与其他相似类型之间的区别,只要他们是从同一基类导出而来的,这种区别通过导出类重写的基类的方法的不同而表示出来;但这些**方法**却是可以通过基类来调用的。
class Instrument{}
class Wind extends Instrument{}
class Bass extends Instrument{}
...
public void play(Instrument i){
...
}
play()方法的参数可以是Instrument也可以是Wind、Bass;但编译器怎样知道引用的是Wind、或者Brass对象呢? 实际上,它们不知道~
编译器无法得知对象类型,程序是如何正确处理这种情况呢?
方法调用绑定
绑定: 将一个方法调用同一个方法主体关联起来;
若在程序执行前进行绑定(由编译器和连接程序实现)叫作前期绑定,面向过程语言中不需要选择就默认的绑定方式。
处理该问题的方法是后期绑定或称为动态绑定、运行时绑定。–必须在对象中安置某种“类型信息”
而java中除了static和final方法(private也属于final方法)外,其他方法都是后期绑定;这句话另外的解读就是:将方法声明为final,还可以有效关闭动态绑定-即-告诉编译器不需要对其进行动态绑定。但对程序的整体性能不会有什么改观(即性能不应该成为你选择final声明的理由)
缺陷
- 不可覆盖私有方法:
public class Rewrite {
private void f(){
System.out.println("private f()");
}
public static void main(String[] args) {
Rewrite re=new Super();
re.f();
}
}
class Super extends Rewrite{
public void f(){
System.out.println("public f()");
}
}//Output: private f()
这段代码很有意思:基类中有main方法,从而,也可以直接运行导出类:执行 java Super。但不论是执行Rewrite还是执行Super 输出结果是相同的~情理之中;
对应的结论:是有非private方法可以被覆盖,但还需要密切关注覆盖private方法:编译器不会报错,但不会按照我们的期望去执行,
域与静态方法
只有普通的方法调用可以是多态的,如果访问某个域,会直接在编译期进行解析,也就不存在多态了~这里只是提出一种现象,通常它是不会发生的,首先,基类的域一般会设置成private,因此不能直接访问。另外,也不大可能对基类中的域和导出类中的域赋予相同的名字,毕竟容易令人混淆。 另外,一个方法是静态的,自然它的行为就不具有多态性,毕竟静态方法是类方法,与单个对象没有任何关系。代码演示:
class Base{
public static void f(){
System.out.println("Base f()");
}
}
public class Rewrite extends Base{
public static void f(){
System.out.println("Rewrite f()");
}
public static void main(String[] args) {
Base re=new Rewrite();
re.f(); //会有warning~
}
}//Output: Base f()
构造器内部的多态行为
首先对于对象调用构造器的顺序:
1) 调用基类构造器。这个步骤会一直递归下去,首先构造这种层次结构的根,然后是下一次导出类,直到最底层的导出类。
2) 按声明顺序调用成员的初始化方法
3)调用导出类构造器的主体
构造器的调用顺序很重要。在进行继承时,我们需要知道基类的一切,并可以调用任何声明为public或protected的成员,这就要求我们在导出类中,基类的所有成员都是有效的。
这样的层次结构也造成了些许的麻烦,在遇到需要清理的问题,需要为新类创建dispose()方法(方法名随意),由于继承的缘故,如果有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法。在覆盖被继承累的dispose()方法时,要注意调用基类的dispose()方法,否则基类的清理动作不会发生。
更复杂一点的,如果成员对象中存在于一个或多个对象共享的情况,参见P161
构造器内部的多态方法的行为
class Glyph{
void draw(){System.out.println("Glyph.draw()");}
Glyph(){
System.out.println("Glyph() bdfore draw()");
draw();
//向外深入到继承层次结构内部,
//调用RoundGlyph.draw()时,该方法的radius
//还没有初始化(初始化动作在导出类的构造器中)
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius=1;
RoundGlyph(int r){
radius=r;
System.out.println("RoundGlyph.RoundGlyph(), radius= "+ radius);
}
void draw(){
System.out.println("RoundGlyph.draw() , radius= "+radius);
}
}
public class Rewrite{
public static void main(String[] args) {
new RoundGlyph(5);
}
}/* Output:
Glyph() bdfore draw()
RoundGlyph.draw() , radius= 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius= 5
*/
出现错误的原因可自行分析;在调用基类构造器之前,需要创建存储空间申请资源,并把分配给对象的存储空间初始化为二进制的0.
因此,在编写构造器时有一条有效的准则:用尽可能简单的方法使对象进入正常状态;如果可以的话避免调用其他方法。 在构造器内唯一能够安全调用的那些方法是基类中的final方法,这些方法不会被覆盖,自然也就不会出现以上代码的问题。
协变返回类型
表示在导出类中的被覆盖方法可以返回去基类方法的返回类型的某种导出类型。
总结
多态意味着“不同的形式”。我们持有从基类继承而来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法。
为在程序中有效的使用多态乃至面向对象的技术,必须扩展自己的编程视野:使其不仅包括个别类的成员和消息,而且还要包括类与类之间的共同特性以及他们之间的关系,从而可以带来:更快的程序开发、代码组织、更好的扩展程序以及更容易的代码维护等。以上,有什么理解不对的方面,还请斧正^_^