JAVA编程思想(4) - 多态(一)

多态

  • 在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本类型。
  • 多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能够创建可扩展程序。

再论向上转型

代码

//: polymorphism/music/Note.java
// Notes to play on musical instruments.
package polymorphism.music;

public enum Note {
    MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~

//: polymorphism/music/Instrument.java
package polymorphism.music;
import static net.mindview.util.Print.*;

class Instrument {
  public void play(Note n) {
    print("Instrument.play()");
  }
}
 ///:~

 //: polymorphism/music/Wind.java
package polymorphism.music;

// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
  // Redefine interface method:
  public void play(Note n) {
    System.out.println("Wind.play() " + n);
  }
} ///:~

//: polymorphism/music/Music.java
// Inheritance & upcasting.
package polymorphism.music;

public class Music {
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    tune(flute); // Upcasting
  }
} /* Output:
Wind.play() MIDDLE_C
*///:~
  • Music.tune()方法接受一个Instrument引用,同时也接受任何导出自Instrument的类。例如Wind是Instrument的导出类,那么当Wind引用传递到tune()方法式,就会出现这种情况,而不需要任何类型转换。这么做是允许的——因为Wind从Instrument继承而来,所以Instrument接口必定存在于Wind中,从Wind向上转型到Instrument可能会“缩小”接口,但不会比Instrument的全部接口更窄。

忘记对象类型

  • 为什么所有人都故意忘记对象的类型呢?在进行向上转型的时候,就会产生这样的情况 ;如果让tune()方法接受一个Wind引用作为自己的参数,似乎会更加直观。但是这样会引发一个重要的问题:如果那样做的话,就需要为Instrument的每种类型都编写一个新的tune方法。假设这样,我们再加入个新的类型的时候就要大量的增加代码。
  • 举个例子
//: polymorphism/music/Music2.java
// Overloading instead of upcasting.
package polymorphism.music;
import static net.mindview.util.Print.*;

class Stringed extends Instrument {
  public void play(Note n) {
    print("Stringed.play() " + n);
  }
}

class Brass extends Instrument {
  public void play(Note n) {
    print("Brass.play() " + n);
  }
}

public class Music2 {
  public static void tune(Wind i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Stringed i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Brass i) {
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    Stringed violin = new Stringed();
    Brass frenchHorn = new Brass();
    tune(flute); // No upcasting
    tune(violin);
    tune(frenchHorn);
  }
} /* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
*///:~
  • 这样做是可以,但是有一个缺点:必须为添加每一个新的Instrument类编写特定类型的方法。这意味着在开始的时候就需要更多的编程,同时在你以后添加类似tune()的新方法或者添加自Instrument导出的新类,仍需要做大量的工作。此外,如果我们忘记重载某个方法,编译器不会放回任何错误信息,这样关于类型的整个处理过程将变得难以控制。
  • 如果我们只写这样一个简单方法,它仅接受基类作为参数,而不是那些特殊的导出类。这样做情况会变好吗?也就是说,如果我们不管导出类的存在,编写的代码只是与基类打交道,会不会更好?而这正是多态所允许的。

转机

  • 观察一段代码
public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDLE_C);
}
  • 它接受的是Instrument引用,那么在这种情况下,编译器是怎么知道这个Instrument引用的指向是Wind对象,而不是Brass对象或者其他呢,实际上,编译器是无法得知的,而这就涉及到了绑定。

方法调用绑定

  • 将一个方法调用同一个方法主体关联起来被称为绑定。如在程序执行前进行绑定,就叫做前期绑定。这是面向过程的语言中不需要选择就默认的绑定方式。例如,C++只有一种方法调用,那就是前期绑定。
  • 但是这并不足以结果上面代码的困惑,解决的办法是后期绑定,它的含义是在运行时根据对象的类型进行绑定,后期绑定也叫做动态绑定或运行时绑定。在这里,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。后期绑定机制随编译语言的不同而有所不同,不管怎样都必须在对象中安置某种”类型信息”。
  • Java除了Static方法和final方法(private方法属于final)之外,其他所有的方法都是后期绑定,这意味着通常情况下,我们不必判定是否进行后期绑定————它会自动发生。
  • 将某个方法声明为final方法会有效的“关闭”动态绑定,这样,编译器就会为final方法调用生成更有效的代码。

产生正确的行为

  • 一旦知道Java中所有方法都是通过动态绑定来实现多态这个事实后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有的导出类都可以正确运行。
//: polymorphism/shape/Shape.java
package polymorphism.shape;

public class Shape {
  public void draw() {}
  public void erase() {}
} ///:~

//: polymorphism/shape/Circle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;

public class Circle extends Shape {
  public void draw() { print("Circle.draw()"); }
  public void erase() { print("Circle.erase()"); }
} ///:~

//: polymorphism/shape/Triangle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;

public class Triangle extends Shape {
  public void draw() { print("Triangle.draw()"); }
  public void erase() { print("Triangle.erase()"); }
} ///:~

//: polymorphism/shape/Square.java
package polymorphism.shape;
import static net.mindview.util.Print.*;

public class Square extends Shape {
  public void draw() { print("Square.draw()"); }
  public void erase() { print("Square.erase()"); }
} ///:~

//: polymorphism/shape/RandomShapeGenerator.java
// A "factory" that randomly creates shapes.
package polymorphism.shape;
import java.util.*;

public class RandomShapeGenerator {
  private Random rand = new Random(47);
  public Shape next() {
    switch(rand.nextInt(3)) {
      default:
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
} ///:~

//: polymorphism/Shapes.java
// Polymorphism in Java.
import polymorphism.shape.*;

public class Shapes {
  private static RandomShapeGenerator gen =
    new RandomShapeGenerator();
  public static void main(String[] args) {
    Shape[] s = new Shape[9];
    // Fill up the array with shapes:
    for(int i = 0; i < s.length; i++)
      s[i] = gen.next();
    // Make polymorphic method calls:
    for(Shape shp : s)
      shp.draw();
  }
} /* Output:
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
*///:~
  • 这段代码就是典型的多态应用了。

可扩展性

  • 由于有了多态机制,我们可以根据自己的需求对系统添加任意多的新类型,而不需要修改代码。在一个良好的OOP程序中,大多数或者所有的方法都是只与基类接口通信的。这样的程序是可扩展的,因为我们可以从通用的基类继承出新的数据类型,从而新添一些功能,那些操作基类接口的方法不需要任何的改动就可以应用于新类。
  • 我们再来看看Instrument这个例子。
//: polymorphism/music3/Music3.java
// An extensible program.
package polymorphism.music3;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;

class Instrument {
  void play(Note n) { print("Instrument.play() " + n); }
  String what() { return "Instrument"; }
  void adjust() { print("Adjusting Instrument"); }
}

class Wind extends Instrument {
  void play(Note n) { print("Wind.play() " + n); }
  String what() { return "Wind"; }
  void adjust() { print("Adjusting Wind"); }
}    

class Percussion extends Instrument {
  void play(Note n) { print("Percussion.play() " + n); }
  String what() { return "Percussion"; }
  void adjust() { print("Adjusting Percussion"); }
}

class Stringed extends Instrument {
  void play(Note n) { print("Stringed.play() " + n); }
  String what() { return "Stringed"; }
  void adjust() { print("Adjusting Stringed"); }
}

class Brass extends Wind {
  void play(Note n) { print("Brass.play() " + n); }
  void adjust() { print("Adjusting Brass"); }
}

class Woodwind extends Wind {
  void play(Note n) { print("Woodwind.play() " + n); }
  String what() { return "Woodwind"; }
}    

public class Music3 {
  // Doesn‘t care about type, so new types
  // added to the system still work right:
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void tuneAll(Instrument[] e) {
    for(Instrument i : e)
      tune(i);
  }
  public static void main(String[] args) {
    // Upcasting during addition to the array:
    Instrument[] orchestra = {
      new Wind(),
      new Percussion(),
      new Stringed(),
      new Brass(),
      new Woodwind()
    };
    tuneAll(orchestra);
  }
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~
  • 事实上,不需要改动tune()方法,所有的新类都能与原有的类一起正确运行,即使tune()是单独存放在某个文件中,并且Instrument接口中添加了其他的新方法,tune()也不需要再编写就能正确运行。
  • 可以看到,tune()方法完全忽略了它周围代码所发生的变化,依旧正常运行,这正是我们期望多态所具有的特性。多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。
时间: 2024-10-04 01:47:17

JAVA编程思想(4) - 多态(一)的相关文章

Java编程思想(五) —— 多态(上)

上一章,Java编程思想(四) -- 复用类里面讲到了向上转型,感觉和多态放在一起写更好. 多态,polymorphism.一个重要的特性,篇幅太长了,分上下两篇写. (1)向上转型 class TV{ public static void show(TV tv){ System.out.println("TV"); } } public class LeTV extends TV{ public static void main(String[] args) { LeTV letv

JAVA编程思想(4) - 多态(三)

若干个对象共享 例如Frog对象拥有其自己的对象,并且知道他们的存活多久,因为Frog对象知道何时调用dispose()去释放其对象.然而,如果这些成员对象中存在于其他一个或多个对象共享的情况,问题将不再简单,不再能简单的调用dispose()了.在这种情况下,我们也许需要引用计数来跟踪依旧访问着共享对象的数量. //: polymorphism/ReferenceCounting.java // Cleaning up shared member objects. import static

Java编程思想(五) —— 多态(下)

多态(上)基本讲解了很多多态的特性和问题.下面继续. 1)构造器和多态 这个问题其实前面写过了,构造器实际上是static方法,只不过是隐式声明,所以构造器并没有多态性. 但是需要知道加载的顺序. class GrandFather{ GrandFather(){ print(); } private int print(){ System.out.println("g"); return 1; } } class Father extends GrandFather{ Father(

Java编程思想(四) —— 复用类

看了老罗罗升阳的专访,情不自禁地佩服,很年轻,我之前以为和罗永浩一个级别的年龄,也是见过的不是初高中编程的一位大牛之一,专访之后,发现老罗也是一步一个脚印的人.别说什么难做,做不了,你根本就没去尝试,也没有去坚持. If you can't fly then run,if you can't run then walk, if you can't walk then crawl,but whatever you do,you have to keep moving forward--Martin

Java编程思想之8多态

这一章看下来,感觉比较混乱.乱感觉主要乱在8.4  8.5. 开始读的时候,感觉8.1 8.2 8.3都挺乱的.读了两遍后发现前三节还是比较有条理的. 8.1主要讲了什么是多态,多态的好处. 8.2主要讲了什么情况会发生多态?? 8.3主要讲了构造器内部里面的方法调用会发生多态. 8.4就一页,而且感觉一般用不到.用到了再看也行. 8.5也很简单,相当于一个总结,一个补充(向下转型) 我主要根据书上的内容,总结两个内容: 1.什么是多态,多态的好处: 2.什么情况下会发生多态?为什么这些情况下会

《Java编程思想(第4版)》pdf

下载地址:网盘下载 内容简介 编辑 本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形.从Java的基础语法到最高级特性(深入的面向对象概念.多线程.自动项目构建.单元测试和调试等),本书都能逐步指导你轻松掌握.[1] 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作.本书的作者拥有多年教学经验,对C.C++以及Java语言都有独到.深入的见解,以通俗易懂及小而直接的示例解释了一个个晦涩抽象的概

Java编程思想重点笔记(Java开发必看)

Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面 试过程中,而且在大型项目开发中也是常用的知识,既有简单的概念理解题(比如is-a关系和has-a关系的区别),也有深入的涉及RTTI和JVM底层 反编译知识. 1. Java中的多态性理解(注意与C++区分) Java中除了static方法和final方法(private方法本质上属于final方法,因为不能被子类访问)之外,其它所有的方法都是动态绑定,这意

【java编程思想--学习笔记(四)】对象导论

写这篇博客的前言: 长话短说,我希望通过阅读<java编程思想>来使我的代码 简洁可用 . 目的的层次不同,首先具体的目标是,了解Java的特性和巩固Java的基础. 更抽象的目的如下: 1.期待以巩固基础的方式,使代码优美,简洁,高效. 2.使自己写的模块能够开放适度,好用. 3.形成一种对代码是否优美的审美观. 于是<Java编程思想>第一章 对象导论 由此开始. 1.1 抽象过程 java 相对于命令式语言的优势在于只针对于待解问题建模.后者所做的主要抽象要求所做问题基于计算

java编程思想-基础

interface: 方法默认为public:成员变量默认 static and final 对象数组的定义:理解? 多接口继承:可以多个接口,但只有一个具体类,具体类在前面 自:多接口继承时,来自不同接口的同名方法怎么处理呢? java重载不能依靠返回类型加以区分(C++可以),也不能依靠checked 异常类型区分 变量定义中的系列定义(逗号隔开):变量名 = 值,其它公共 自:类中,自己引用自己的理解(如,链表节点元素).静态看成动态,编译器的本质实现 内部类和普通类区别:内部类可priv