您好,登錄后才能下訂單哦!
本篇內容介紹了“如何理解Java編程多態”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
一、向上轉型
二、轉機
1、綁定
2、擴展性
3、缺陷
三、構造器與多態
1、構造器的調用順序
2、構造器內部的多態方法的行為
四、協變返回類型
五、繼承進行設計
前言:
封裝,是合并屬性和行為創建一種新的數據類型,繼承是建立數據類型之間的某種關系(is-a),而多態就是這種關系在實際場景的運用。
多態就是把做什么和怎么做分開了;其中,做什么是指調用的哪個方法[play 樂器],怎么做是指實現方案[使用A樂器 使用B樂器],''分開了''指兩件事不在同一時間確定。
對象既可以作為它本身的類型使用,也可以作為它的基類型使用,而這種把對某個對象的引用視為對其基類型的引用的做法就是向上轉型。
example:
public enum Note { // 演奏樂符 MIDDLE_C, C_SHARP, B_FLAT; } public class Instrument { // 樂器基類 public void play(Note n) { print("Instrument.play()"); } } public class Wind extends Instrument{ // Wind是一個具體的樂器 // Redefine interface method: public void play(Note n) { System.out.println("Wind.play() " + n); } } 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); // 向上轉型 } }
好處:
在上面例子中,如果讓tune
方法接受一個Wind
引用作為自己的參數,似乎看起來更為直觀,但是會引發一個問題:這個時候你就需要為系統中Instrument
的每種類型都編寫一個新的tune
方法。所以我們只寫一個簡單的方法,僅僅接收基類作為參數,而不是特殊的導出類,這么做情況不是變得更好嗎。
example:
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); } }
public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); }
在上面這個方法中,它接收一個Instrument
引用,那么在這種情況下,編譯器怎么樣才能知道這個instrument
引用指向的是Wind
對象呢? ——通過后期綁定
將一個方法調用同一個方法主體關聯起來稱為綁定。
在程序執行前進行綁定,就是前期綁定,比如C語言就只有一種方法調用,就是前期綁定。
在運行時根據對象的類型進行綁定就是后期綁定,也叫做動態綁定或者運行時綁定。
Java
中除了static
方法和final
方法之外,其它所有方法都是后期綁定,這意味著通常情況下,我們不必判定是否應該進行后期綁定——它會自動發生。
由于有多態機制,所以可根據自己的需要向系統里加入任意多的新類型,同時毋需更改 true()
方法。在一個設計良好的 OOP 程序中,我們的大多數或者所有方法都會遵從 tune()
的模型,而且只與基礎類接口通信。我們說這樣的程序具有“擴展性”,因為可以從通用的基礎類繼承新的數據類型,從而新添一些功能。如果是為了適應新類的要求,那么對基礎類接口進行操縱的方法根本不需要改變,
對于樂器例子,假設我們在基礎類里加入更多的方法[what/adjust],
以及一系列新類[Woodwind/Brass],
,
例子:
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); } }
為樂器系統添加更多的類型,而不用改動tune
方法。tune
方法完全可以忽略它周圍代碼所發生的全部變化,依舊正常運行。
私有方法
private
方法被自動修飾為final
,而且對導出類是屏蔽的,所以在子類Derived
類中的f方法是一個全新的方法。既然基類中的f方法在在子類Derived
中不可見,那么也不能被重載。
域與靜態方法
任何域(field)
的訪問操作都是由編譯器解析的,因此不是多態的。
如果某個方法是靜態的,那么它就不具有多態性
通常,構造器不同于其它方法,涉及到多態時也是如此。構造器是不具有多態性的
基類的構造器總是在導出類的構造過程中被調用,而且按照繼承層次逐漸向上鏈接。使得每個基類的構造器都能得到調用。
構造器調用的層次結構帶來一個問題:如果在一個構造器內部調用正在構造的對象的某個動態綁定方法,會發生什么?
public class Glyph { void draw() { print("Glyph.draw()"); } Glyph() { print("Glyph() before draw()"); draw(); // 調用正在構造的對象的某個動態綁定方法,對象的字段radius被初始化為0 print("Glyph() after draw()"); } } public class RoundGlyph extends Glyph{ private int radius = 1; RoundGlyph(int r) { radius = r; print("RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { print("RoundGlyph.draw(), radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } /* Output: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 *///:~
在Glyph
的構造器中,我們調用了draw
方法,因為這個是動態綁定方法的緣故,我們就會調用導出類RoundGlyph
中的draw方法,但是這個方法操縱的成員radius
還沒初始化,所以就體現出問題了,結果中第一次輸出radius
為0。
所以初始化的實際過程是:
1 在其他任何事物之前,將分配給對象的存儲空間初始化成二進制的零
2 如前所述調用基類構造器
3 按照聲明的順序調用成員的初始化方法
4 調用導出類的構造器主體
在面向對象程序設計中,協變返回類型指的是子類中的成員函數的返回值類型不必嚴格等同于父類中被重寫的成員函數的返回值類型,而可以是更 "狹窄" 的類型。
Java 5.0
添加了對協變返回類型的支持,即子類覆蓋(即重寫)基類方法時,返回的類型可以是基類方法返回類型的子類。協變返回類型允許返回更為具體的類型。
例子:
import java.io.ByteArrayInputStream; import java.io.InputStream; class Base { //子類Derive將重寫此方法,將返回類型設置為InputStream的子類 public InputStream getInput() { return System.in; } } public class Derive extends Base { @Override public ByteArrayInputStream getInput() { return new ByteArrayInputStream(new byte[1024]); } public static void main(String[] args) { Derive d=new Derive(); System.out.println(d.getInput().getClass()); } } /*程序輸出: class java.io.ByteArrayInputStream */
class Actor { public void act() { } } class HappyActor extends Actor { public void act() { System.out.println("HappyActor"); } } class SadActor extends Actor { public void act() { System.out.println("SadActor"); } } class Stage { private Actor actor = new HappyActor(); public void change() { actor = new SadActor(); } public void performPlay() { actor.act(); } } public class Transmogrify { public static void main(String[] args) { Stage stage = new Stage(); stage.performPlay(); stage.change(); stage.performPlay(); } }
輸出:
HappyActor
SadActor
一條通用的準則是:“用繼承表達行為間的差異,并用字段表達狀態上的變化”。在上述例子中,兩者都用到了:通過繼承得到了兩個不同的類,用于表達 act()
方法的差異:而 Stage
通過運用組合使自己的狀態發生了變化。在這種情況下,這種狀態的改變也就產生了行為的改變。
“如何理解Java編程多態”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。