1. 程式人生 > >JAVA程式設計思想(4)

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的類。例如WindInstrument的匯出類,那麼當Wind引用傳遞到tune()方法式,就會出現這種情況,而不需要任何型別轉換。這麼做是允許的——因為WindInstrument繼承而來,所以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()方法完全忽略了它周圍程式碼所發生的變化,依舊正常執行,這正是我們期望多型所具有的特性。多型是一項讓程式設計師“將改變的事物與未變的事物分離開來”的重要技術。

相關推薦

Java程式設計思想4讀後小記

3.4 本節講到Java的賦值的內容,需要區別的是基本型別(int,bool)的賦值和物件的賦值不太一樣。物件之間的賦值,實際是將"引用"從一個地方複製到另一個地方。這意味著如果對物件使用c=d,那麼c和d都指向原本只有d指向的那個物件,這個時候對c或者對d物件做更改,c和

JAVA程式設計思想(4)

多型 在面向物件的程式設計語言中,多型是繼資料抽象和繼承之後的第三種基本型別。多型通過分離做什麼和怎麼做,從另一個角度將介面和實現分離開來。多型不但能夠改善程式碼的組織結構和可讀性,還能夠建立可擴充套件程式。 再論向上轉型 程式碼 //: polymorphism/mu

Java程式設計思想4

第13章 字串 1 String物件是不可變,String類中每一個修改String值的方法實際都是建立一個全新的String物件 2 " + "與" += "是Java中僅有的兩個過載過的操作符,而Java並不允許過載任何操作符。 3 mac下用Java命令 package c06

java程式設計思想 第二章 (一切都是物件)練習 2.11 練習4

練習4:將DataOnly程式碼段改寫成一個程式,然後編譯、執行。 DateOnly程式碼段在《Java程式設計思想》第26頁。 程式碼段為: class DataOnly{ int i; double d; boolean b; } 程式碼改寫:

Java程式設計思想(第4版)》電子書附下載連結+30個總結JVM虛擬機器的技術文排版好(收藏版)

技術書閱讀方法論 一.速讀一遍(最好在1~2天內完成) 人的大腦記憶力有限,在一天內快速看完一本書會在大腦裡留下深刻印象,對於之後複習以及總結都會有特別好的作用。 對於每一章的知識,先閱讀標題,弄懂大概講的是什麼主題,再去快速看一遍,不懂也沒有關係,但是一定要在不懂的

Java程式設計思想(第4版)》pdf附網盤下載連結送給還在迷茫的你

技術書閱讀方法論 一.速讀一遍(最好在1~2天內完成) 人的大腦記憶力有限,在一天內快速看完一本書會在大腦裡留下深刻印象,對於之後複習以及總結都會有特別好的作用。 對於每一章的知識,先閱讀標題,弄懂大概講的是什麼主題,再去快速看一遍,不懂也沒有關係,但是一定要在不懂的

Java程式設計思想-練習題(4.7)

預設構造器建立一個類(沒有自變數),列印一條訊息。建立屬於這個類的一個物件。 class Bowl { Bowl(){ System.out.println("this

Java程式設計思想(第4版) 之 15.5 泛型之匿名內部類

15.5   匿名內部類 泛型還可以應用於內部類以及匿名內部類。下面的示例使用匿名內部類實現了Generator介面:     Customer和Teller類都只有private的構造器,這可以強制你必須使用Generator物件。Customer有一個generator(

JAVA程式設計思想(第4版)賦值小結

賦值使用操作符“=”。對基本資料型別的賦值是很簡單的。基本資料型別儲存了實際的數值,而並非指向一個物件的引用,所以在為其賦值的時候,是直接將一個地方的內容複製到了另一個地方。例如:對基本資料型別使用

JAVA程式設計思想(第4版) 在構造器中呼叫構造器

可能為一個類寫了多個構造器,為了能夠在一個構造器中呼叫另一個構造器,必須用到this關鍵字,this指"這個物件",表示對當前物件的引用。舉個例子: package test; public class Flower { int petalCount=0; Stri

JAVA程式設計思想(第4版)物件終結條件,system.gc(),finalize()一部分用法小結

finalize()有一個有趣的用法,它並不依賴於每次都要對finalize()進行呼叫,這就是物件終結條件的驗證。 當對某個物件不再感興趣----也就是它可以被清理了,這個物件應該處於某種狀態,使它佔用的記憶體可以被安全地釋放。 例如,要是物件代表了一個開啟的檔案,在物件

Java程式設計思想(第4版)(帶目錄書籤)

    從本書獲得的各項大獎以及來自世界各地的讀者評論中,不難看出這是一本經典之作。本書的作者擁有多年教學經驗,對C、C++以及Java語言都有獨到、深入的見解,以通俗易懂及小而直接的示例解釋了一個個晦澀抽象的概念。本書共22章,包括操作符、控制執行流程、訪問許可權控制、複用

java程式設計思想4版初始學習

前言 1、看過這部分內容,首先我瞭解到作者通過和其他程式語言的比較強調了java程式設計對複雜性的優勢。 2、 以作者原話,這本書的誕生是因為java語言的升級,“這本書基本可以稱為‘只限

Java程式設計思想4版-第六章

第6章 訪問許可權控制 訪問控制(或隱藏具體實現)與“最初的實現並不恰當”有關。 所有優秀的作者,包括那些編寫軟體的程式設計師,都清楚其著作的某些部分直至重新創作的時候才變得完美,有時甚至要反覆重寫多次。如果你把一個程式碼段放到了某個位置,等過一會兒回頭再看

Thinking in Java 4th(Java程式設計思想第四版)文件、原始碼、習題答案

  Thinking in Java 4th 中、英文兩版pdf文件,書中原始碼及課後習題答案。連結:https://pan.baidu.com/s/1BKJdtgJ3s-_rN1OB4rpLTQ 密碼:2zc4   http://greggordon.org/java/tij4/solutions.

Java程式設計思想學習筆記-第11章

.title { text-align: center; margin-bottom: .2em } .subtitle { text-align: center; font-size: medium; font-weight: bold; margin-top: 0 } .todo { font-famil

JAVA程式設計思想第七章-複用類

1.一個物件被轉換成string時,會呼叫物件的toSting方法 public class demo7 { private water w=new water(); private String s="string"; public static void main(Strin

【學習筆記】 唐大仕—Java程式設計4講 類、包和介面之4.2 類的繼承

【學習筆記】 唐大仕—Java程式設計 第4講 類、包和介面之4.2 類的繼承 super的使用 1.使用super訪問父類的域和方法 注意:正是由於繼承,使用this可以訪問父類的域和方法。但是有時為了明確指明父類的域和方法,就要用關鍵字super。this和super都是指當前同一個物件

【學習筆記】 唐大仕—Java程式設計4講 類、包和介面之4.4 訪問修飾符

修飾符(modifiers) 訪問修飾符(access modifiers) 如public/private等 其他修飾符 如abstract等 可以修飾類、也可以修飾類的成員(欄位、方法)   同一個類中 同一個包中 不同包中的子類

【學習筆記】 唐大仕—Java程式設計4講 類、包和介面之4.6 介面

【學習筆記】 唐大仕—Java程式設計 第4講 類、包和介面之4.6 介面 介面(interface) 介面,某種特徵的約定 定義介面interface  所有方法都自動是public abstract 實現介面implements  可以實現多繼承  與類的繼承關係無關 面向介面程式設計,而不