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()方法完全忽略了它周圍程式碼所發生的變化,依舊正常執行,這正是我們期望多型所具有的特性。多型是一項讓程式設計師“將改變的事物與未變的事物分離開來”的重要技術。
相關推薦
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 可以實現多繼承 與類的繼承關係無關 面向介面程式設計,而不