熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

理解java的多形性

2013-11-15 11:48:55  來源: JSP教程 

  對於面向對象的程序設計語言多型性是第三種最基本的特征(前兩種是數據抽象和繼承
  
  多形性(Polymorphism)從另一個角度將接口從具體的實施細節中分離出來亦即實現了是什麼怎樣做兩個模塊的分離利用多形性的概念代碼的組織以及可讀性均能獲得改善此外還能創建易於擴展的程序無論在項目的創建過程中還是在需要加入新特性的時候它們都可以方便地成長
  通過合並各種特征與行為封裝技術可創建出新的數據類型通過對具體實施細節的隱藏可將接口與實施細節分離使所有細節成為private(私有)這種組織方式使那些有程序化編程背景人感覺頗為舒適但多形性卻涉及對類型的分解通過上一章的學習大家已知道通過繼承可將一個對象當作它自己的類型或者它自己的基礎類型對待這種能力是十分重要的因為多個類型(從相同的基礎類型中衍生出來)可被當作同一種類型對待而且只需一段代碼即可對所有不同的類型進行同樣的處理利用具有多形性的方法調用一種類型可將自己與另一種相似的類型區分開只要它們都是從相同的基礎類型中衍生出來的這種區分是通過各種方法在行為上的差異實現的可通過基礎類實現對那些方法的調用
  在這一章中大家要由淺入深地學習有關多形性的問題(也叫作動態綁定推遲綁定或者運行期綁定)同時舉一些簡單的例子其中所有無關的部分都已剝除只保留與多形性有關的代碼
  
   上溯造型
  在第大家已知道可將一個對象作為它自己的類型使用或者作為它的基礎類型的一個對象使用取得一個對象句柄並將其作為基礎類型句柄使用的行為就叫作上溯造型——因為繼承樹的畫法是基礎類位於最上方
  但這樣做也會遇到一個問題如下例所示(若執行這個程序遇到麻煩請參考第章的小節賦值
  
  
  //: Musicjava
  // Inheritance & upcasting
  package c;
  
  class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
  middleC = new Note()
  cSharp = new Note()
  cFlat = new Note();
  } // Etc
  
  class Instrument {
  public void play(Note n) {
  Systemoutprintln(Instrumentplay());
  }
  }
  
  // Wind objects are instruments
  // because they have the same interface:
  class Wind extends Instrument {
  // Redefine interface method:
  public void play(Note n) {
  Systemoutprintln(Windplay());
  }
  }
  
  public class Music {
  public static void tune(Instrument i) {
  //
  iplay(NotemiddleC);
  }
  public static void main(String[] args) {
  Wind flute = new Wind();
  tune(flute); // Upcasting
  }
  } ///:~
  
  其中方法Musictune()接收一個Instrument句柄同時也接收從Instrument衍生出來的所有東西當一個Wind句柄傳遞給tune()的時候就會出現這種情況此時沒有造型的必要這樣做是可以接受的Instrument裡的接口必須存在於Wind中因為Wind是從Instrument裡繼承得到的從Wind向Instrument的上溯造型可能縮小那個接口但不可能把它變得比Instrument的完整接口還要小
  
   為什麼要上溯造型
  這個程序看起來也許顯得有些奇怪為什麼所有人都應該有意忘記一個對象的類型呢?進行上溯造型時就可能產生這方面的疑惑而且如果讓tune()簡單地取得一個Wind句柄將其作為自己的自變量使用似乎會更加簡單直觀得多但要注意假如那樣做就需為系統內Instrument的每種類型寫一個全新的tune()假設按照前面的推論加入Stringed(弦樂)和Brass(銅管)這兩種Instrument(樂器)
  
  //: Musicjava
  // Overloading instead of upcasting
  
  class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
  middleC = new Note()
  cSharp = new Note()
  cFlat = new Note();
  } // Etc
  
  class Instrument {
  public void play(Note n) {
  Systemoutprintln(Instrumentplay());
  }
  }
  
  class Wind extends Instrument {
  public void play(Note n) {
  Systemoutprintln(Windplay());
  }
  }
  
  class Stringed extends Instrument {
  public void play(Note n) {
  Systemoutprintln(Stringedplay());
  }
  }
  
  class Brass extends Instrument {
  public void play(Note n) {
  Systemoutprintln(Brassplay());
  }
  }
  
  public class Music {
  public static void tune(Wind i) {
  iplay(NotemiddleC);
  }
  public static void tune(Stringed i) {
  iplay(NotemiddleC);
  }
  public static void tune(Brass i) {
  iplay(NotemiddleC);
  }
  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);
  }
  } ///:~
  
  這樣做當然行得通但卻存在一個極大的弊端必須為每種新增的Instrument類編寫與類緊密相關的方法這意味著第一次就要求多得多的編程量以後假如想添加一個象tune()那樣的新方法或者為Instrument添加一個新類型仍然需要進行大量編碼工作此外即使忘記對自己的某個方法進行過載設置編譯器也不會提示任何錯誤這樣一來類型的整個操作過程就顯得極難管理有失控的危險
  但假如只寫一個方法將基礎類作為自變量或參數使用而不是使用那些特定的衍生類豈不是會簡單得多?也就是說如果我們能不顧衍生類只讓自己的代碼與基礎類打交道那麼省下的工作量將是難以估計的
  這正是多形性大顯身手的地方然而大多數程序員(特別是有程序化編程背景的)對於多形性的工作原理仍然顯得有些生疏
  
   深入理解
  對於Musicjava的困難性可通過運行程序加以體會輸出是Windplay()這當然是我們希望的輸出但它看起來似乎並不願按我們的希望行事請觀察一下tune()方法
  
  public static void tune(Instrument i) {
  //
  iplay(NotemiddleC);
  }
  
  它接收Instrument句柄所以在這種情況下編譯器怎樣才能知道Instrument句柄指向的是一個Wind而不是一個Brass或Stringed呢?編譯器無從得知為了深入了理解這個問題我們有必要探討一下綁定這個主題
  
   方法調用的綁定
  將一個方法調用同一個方法主體連接到一起就稱為綁定(Binding)若在程序運行以前執行綁定(由編譯器和鏈接程序如果有的話)就叫作早期綁定大家以前或許從未聽說過這個術語因為它在任何程序化語言裡都是不可能的C編譯器只有一種方法調用那就是早期綁定
  上述程序最令人迷惑不解的地方全與早期綁定有關因為在只有一個Instrument句柄的前提下編譯器不知道具體該調用哪個方法
  解決的方法就是後期綁定它意味著綁定在運行期間進行以對象的類型為基礎後期綁定也叫作動態綁定運行期綁定若一種語言實現了後期綁定同時必須提供一些機制可在運行期間判斷對象的類型並分別調用適當的方法也就是說編譯器此時依然不知道對象的類型但方法調用機制能自己去調查找到正確的方法主體不同的語言對後期綁定的實現方法是有所區別的但我們至少可以這樣認為它們都要在對象中安插某些特殊類型的信息
  Java中綁定的所有方法都采用後期綁定技術除非一個方法已被聲明成final這意味著我們通常不必決定是否應進行後期綁定——它是自動發生的
  為什麼要把一個方法聲明成final呢?正如上一章指出的那樣它能防止其他人覆蓋那個方法但也許更重要的一點是它可有效地關閉動態綁定或者告訴編譯器不需要進行動態綁定這樣一來編譯器就可為final方法調用生成效率更高的代碼
  
   產生正確的行為
  知道Java裡綁定的所有方法都通過後期綁定具有多形性以後就可以相應地編寫自己的代碼令其與基礎類溝通此時所有的衍生類都保證能用相同的代碼正常地工作或者換用另一種方法我們可以將一條消息發給一個對象讓對象自行判斷要做什麼事情
  在面向對象的程序設計中有一個經典的形狀例子由於它很容易用可視化的形式表現出來所以經常都用它說明問題但很不幸的是它可能誤導初學者認為OOP只是為圖形化編程設計的這種認識當然是錯誤的
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19669.html
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.