<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 7.2 深入理解 對于`Music.java`的困難性,可通過運行程序加以體會。輸出是`Wind.play()`。這當然是我們希望的輸出,但它看起來似乎并不愿按我們的希望行事。請觀察一下`tune()`方法: ``` public static void tune(Instrument i) { // ... i.play(Note.middleC); } ``` 它接收`Instrument`引用。所以在這種情況下,編譯器怎樣才能知道`Instrument`引用指向的是一個`Wind`,而不是一個`Brass`或`Stringed`呢?編譯器無從得知。為了深入了理解這個問題,我們有必要探討一下“綁定”這個主題。 ## 7.2.1 方法調用的綁定 將一個方法調用同一個方法主體連接到一起就稱為“綁定”(Binding)。若在程序運行以前執行綁定(由編譯器和鏈接程序,如果有的話),就叫作“早期綁定”。大家以前或許從未聽說過這個術語,因為它在任何程序化語言里都是不可能的。C編譯器只有一種方法調用,那就是“早期綁定”。 上述程序最令人迷惑不解的地方全與早期綁定有關,因為在只有一個`Instrument`引用的前提下,編譯器不知道具體該調用哪個方法。 解決的方法就是“后期綁定”,它意味著綁定在運行期間進行,以對象的類型為基礎。后期綁定也叫作“動態綁定”或“運行期綁定”。若一種語言實現了后期綁定,同時必須提供一些機制,可在運行期間判斷對象的類型,并分別調用適當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能自己去調查,找到正確的方法主體。不同的語言對后期綁定的實現方法是有所區別的。但我們至少可以這樣認為:它們都要在對象中安插某些特殊類型的信息。 Java中綁定的所有方法都采用后期綁定技術,除非一個方法已被聲明成`final`。這意味著我們通常不必決定是否應進行后期綁定——它是自動發生的。 為什么要把一個方法聲明成`final`呢?正如上一章指出的那樣,它能防止其他人覆蓋那個方法。但也許更重要的一點是,它可有效地“關閉”動態綁定,或者告訴編譯器不需要進行動態綁定。這樣一來,編譯器就可為`final`方法調用生成效率更高的代碼。 ## 7.2.2 產生正確的行為 知道Java里綁定的所有方法都通過后期綁定具有多態性以后,就可以相應地編寫自己的代碼,令其與基類溝通。此時,所有的派生類都保證能用相同的代碼正常地工作。或者換用另一種方法,我們可以“將一條消息發給一個對象,讓對象自行判斷要做什么事情。” 在面向對象的程序設計中,有一個經典的“形狀”例子。由于它很容易用可視化的形式表現出來,所以經常都用它說明問題。但很不幸的是,它可能誤導初學者認為OOP只是為圖形化編程設計的,這種認識當然是錯誤的。 形狀例子有一個基類,名為`Shape`;另外還有大量派生類型:`Circle`(圓形),`Square`(方形),`Triangle`(三角形)等等。大家之所以喜歡這個例子,因為很容易理解“圓屬于形狀的一種類型”等概念。下面這幅繼承圖向我們展示了它們的關系: ![](https://box.kancloud.cn/0859fb072a3f03cb9e8086854ea7528c_424x214.gif) 向上轉換可用下面這個語句簡單地表現出來: ``` Shape s = new Circle(); ``` 在這里,我們創建了`Circle`對象,并將結果引用立即賦給一個`Shape`。這表面看起來似乎屬于錯誤操作(將一種類型分配給另一個),但實際是完全可行的——因為按照繼承關系,`Circle`屬于`Shape`的一種。因此編譯器認可上述語句,不會向我們提示一條出錯消息。 當我們調用其中一個基類方法時(已在派生類里覆蓋): ``` s.draw(); ``` 同樣地,大家也許認為會調用`Shape`的`draw()`,因為這畢竟是一個`Shape`引用。那么編譯器怎樣才能知道該做其他任何事情呢?但此時實際調用的是`Circle.draw()`,因為后期綁定已經介入(多態性)。 下面這個例子從一個稍微不同的角度說明了問題: ``` //: Shapes.java // Polymorphism in Java class Shape { void draw() {} void erase() {} } class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); } void erase() { System.out.println("Circle.erase()"); } } class Square extends Shape { void draw() { System.out.println("Square.draw()"); } void erase() { System.out.println("Square.erase()"); } } class Triangle extends Shape { void draw() { System.out.println("Triangle.draw()"); } void erase() { System.out.println("Triangle.erase()"); } } public class Shapes { public static Shape randShape() { switch((int)(Math.random() * 3)) { default: // To quiet the compiler case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } 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] = randShape(); // Make polymorphic method calls: for(int i = 0; i < s.length; i++) s[i].draw(); } } ///:~ ``` 針對從`Shape`派生出來的所有東西,`Shape`建立了一個通用接口——也就是說,所有(幾何)形狀都可以描繪和刪除。派生類覆蓋了這些定義,為每種特殊類型的幾何形狀都提供了獨一無二的行為。 在主類`Shapes`里,包含了一個`static`方法,名為`randShape()`。它的作用是在每次調用它時為某個隨機選擇的`Shape`對象生成一個引用。請注意向上轉換是在每個`return`語句里發生的。這個語句取得指向一個`Circle`,`Square`或者`Triangle`的引用,并將其作為返回類型`Shape`發給方法。所以無論什么時候調用這個方法,就絕對沒機會了解它的具體類型到底是什么,因為肯定會獲得一個單純的`Shape`引用。 `main()`包含了`Shape`引用的一個數組,其中的數據通過對`randShape()`的調用填入。在這個時候,我們知道自己擁有`Shape`,但不知除此之外任何具體的情況(編譯器同樣不知)。然而,當我們在這個數組里步進,并為每個元素調用`draw()`的時候,與各類型有關的正確行為會魔術般地發生,就象下面這個輸出示例展示的那樣: ``` Circle.draw() Triangle.draw() Circle.draw() Circle.draw() Circle.draw() Square.draw() Triangle.draw() Square.draw() Square.draw() ``` 當然,由于幾何形狀是每次隨機選擇的,所以每次運行都可能有不同的結果。之所以要突出形狀的隨機選擇,是為了讓大家深刻體會這一點:為了在編譯的時候發出正確的調用,編譯器毋需獲得任何特殊的情報。對`draw()`的所有調用都是通過動態綁定進行的。 ## 7.2.3 擴展性 現在,讓我們仍然返回樂器(I`nstrument`)示例。由于存在多態性,所以可根據自己的需要向系統里加入任意多的新類型,同時毋需更改`true()`方法。在一個設計良好的OOP程序中,我們的大多數或者所有方法都會遵從`tune()`的模型,而且只與基類接口通信。我們說這樣的程序具有“擴展性”,因為可以從通用的基類繼承新的數據類型,從而新添一些功能。如果是為了適應新類的要求,那么對基類接口進行操縱的方法根本不需要改變,對于樂器例子,假設我們在基類里加入更多的方法,以及一系列新類,那么會出現什么情況呢?下面是示意圖: ![](https://box.kancloud.cn/8a046c43b56c32f95c1f2b470fa7c805_364x400.gif) 所有這些新類都能與老類——`tune()`默契地工作,毋需對`tune()`作任何調整。即使`tune()`位于一個獨立的文件里,而將新方法添加到`Instrument`的接口,`tune()`也能正確地工作,不需要重新編譯。下面這個程序是對上述示意圖的具體實現: ``` //: Music3.java // An extensible program import java.util.*; class Instrument3 { public void play() { System.out.println("Instrument3.play()"); } public String what() { return "Instrument3"; } public void adjust() {} } class Wind3 extends Instrument3 { public void play() { System.out.println("Wind3.play()"); } public String what() { return "Wind3"; } public void adjust() {} } class Percussion3 extends Instrument3 { public void play() { System.out.println("Percussion3.play()"); } public String what() { return "Percussion3"; } public void adjust() {} } class Stringed3 extends Instrument3 { public void play() { System.out.println("Stringed3.play()"); } public String what() { return "Stringed3"; } public void adjust() {} } class Brass3 extends Wind3 { public void play() { System.out.println("Brass3.play()"); } public void adjust() { System.out.println("Brass3.adjust()"); } } class Woodwind3 extends Wind3 { public void play() { System.out.println("Woodwind3.play()"); } public String what() { return "Woodwind3"; } } public class Music3 { // Doesn't care about type, so new types // added to the system still work right: static void tune(Instrument3 i) { // ... i.play(); } static void tuneAll(Instrument3[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument3[] orchestra = new Instrument3[5]; int i = 0; // Upcasting during addition to the array: orchestra[i++] = new Wind3(); orchestra[i++] = new Percussion3(); orchestra[i++] = new Stringed3(); orchestra[i++] = new Brass3(); orchestra[i++] = new Woodwind3(); tuneAll(orchestra); } } ///:~ ``` 新方法是`what()`和`adjust()`。前者返回一個`String`引用,同時返回對那個類的說明;后者使我們能對每種樂器進行調整。 在`main()`中,當我們將某樣東西置入`Instrument3`數組時,就會自動向上轉換到`Instrument3`。 可以看到,在圍繞`tune()`方法的其他所有代碼都發生變化的同時,`tune()`方法卻絲毫不受它們的影響,依然故我地正常工作。這正是利用多態性希望達到的目標。我們對代碼進行修改后,不會對程序中不應受到影響的部分造成影響。此外,我們認為多態性是一種至關重要的技術,它允許程序員“將發生改變的東西同沒有發生改變的東西區分開”。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看