java抽象類和接口和繼承之間關係

有時候,我們可能想要構造一個很抽象的父類對象,它可能僅僅代表一個分類或抽象概念,它的實例沒有任何意義,因此不希望它能被實例化。例如:有一個父類“ 水果(Fruit)”,它有幾個子類“蘋果(Apple)”、“橘子(Orange)”、“香蕉(Banana)”等。水果在這裏僅僅只是作爲一個分類,顯然水果的實例沒有什麼意義(就好像一個人如果告訴你他買了一些水果但是卻不告訴你是蘋果還是橘子,你很難想象他到底買的是什麼。)。而水果類又要能被子類化,這就要求我們使用抽象類(abstract class)來解決這個問題。
在java中,通過在class關鍵字前增加abstract修飾符,就可以將一個類定義成抽象類。抽象類不能被實例化。例如:
          定義抽象類水果(Fruit)
          public abstract class Fruit {
                  ……
          }
           如果我們試圖用以下語句來獲得一個實例,將無法編譯成功。
           Fruit fruit = new Fruit();
而我們仍然可以構造水果類的子類,如:
          子類“蘋果(Apple)”
           public class Apple extends Fruit {
                   ……
           }
          子類“橘子(Orange)”
           public class Orange extends Fruit {
                   ……
           }
這樣就達到我們的目的了。
抽象類除了能象普通類一樣可以擁有一般的屬性和方法,也可以擁有抽象方法(abstract method)。例如:
           抽象類“形狀(Shape)”擁有抽象方法draw()。
           public abstract class Shape {
                  ……
                  public abstract void draw();
                  ……
           }
抽象方法與抽象的行爲相對應,通常是這個行爲對父對象沒有意義,而子對象有具體動作。例如方法draw()對於類Shape沒有意義,而類Shape的子類矩形(Rectangle)的方法draw()可以有實際的動作(根據矩形的四個頂點畫出矩形的四個邊),子類圓(Circle)的方法draw()也可以有實際的動作(根據圓心和半徑畫出圓周)。
抽象類可以有抽象方法也可以沒有抽象方法;但是如果一個類有抽象方法,那這個類只能定義爲抽象類。
           如果按照以下代碼類“形狀(Shape)”仍然擁有抽象方法draw(),但沒有定義爲抽象類,將會編譯失敗。
           public class Shape {
                  ……
                  public abstract void draw();
                  ……
           }
抽象方法還有一個特點是,它強迫子類要麼仍然保持抽象性(即不具體實現該方法並仍然定義爲抽象類),要麼具體表現出這個方法的行爲(實現具體的動作或者通過拋出UnsupportedOperationException異常來表明不支持該行爲)。這樣也可以強化多態性。
上面簡要分析了抽象類,下面談談接口(interface)。java語言使用關鍵字interface定義一個接口。接口也是抽象對象,它甚至比抽象類更抽象。接口中的方法都是抽象方法。
一個接口可以繼承其他接口;一個類通過關鍵字implements聲明要實現一個接口,並具體實現接口的方法。
           例如:有一個接口InterfaceA,

Java代碼
  1. public   interface  InterfaceA {   
  2.          void  methodA();   
  3. }  
 

           類ClassA實現接口InterfaceA。

Java代碼
  1. public   class  ClassA implements InterfaceA {   
  2.           public   void  methodA() {   
  3.                System.out.println( "methodA of ClassA implements InterfaceA" );   
  4.          }   
  5. }  
 

如果是抽象類實現一個接口,那麼抽象類中可以不具體實現接口的方法(保持其抽象性),而由其子類去實現。
           抽象類ClassB實現接口InterfaceA,但是沒有具體實現方法methodA(),

Java代碼
  1. public   abstract   class  ClassB  {           }  
 

           子類ClassBSub實現接口InterfaceA,但是沒有具體實現方法methodA(),

Java代碼
  1. public   class  ClassBSub {   
  2.          public   void  methodA() {   
  3.               System.out.println( "methodA of ClassBSub the subclass of ClassB" );   
  4.         }   
  5. }  
 

接口和抽象類顯著的共同點是接口和抽象類都可以有抽象方法。
接口和抽象類的不同點有:
           (1)抽象類可以有實例變量,而接口不能擁有實例變量,接口中的變量都是靜態(static)的常量(final)。
           (2)抽象類可以有非抽象方法,而接口只能有抽象方法。
java中,類與類之間是不能多繼承的。java之所以禁止類與類之間的多繼承是因爲多繼承有很大的缺點。
多繼承雖然能使子類同時擁有多個父類的特徵,但是其缺點也是很顯著的,主要有兩方面:
(1)如果在一個子類繼承的多個父類中擁有相同名字的實例變量,子類在引用該變量時將產生歧義,無法判斷應該使用哪個父類的變量。例如:
           類ClassA:

Java代碼
  1. public   class  ClassA {   
  2.         protected   int  varSame =  0 ;   
  3. }  
 

           類ClassB:

Java代碼
  1. public   class  ClassB {   
  2.          protected   int  varSame =  1 ;   
  3. }  
 

           子類ClassC:(假設允許類與類之間多繼承)

Java代碼
  1. public   class  ClassC  extends  ClassA, ClassB {   
  2.          public   void  printOut() {   
  3.                 System.out.println( super .varSame);   
  4.         }   
  5.          public   static   void  main(String[] args) {   
  6.                 ClassC classC =  new  ClassC();   
  7.                 classC.printOut();   
  8.         }   
  9. }  
 


           上面程序的運行結果會是什麼呢?輸出0還是1?
(2)如果在一個子類繼承的多個父類中擁有相同方法,子類中有沒有覆蓋該方法,那麼調用該方法時將產生歧義,無法判斷應該調用哪個父類的方法。例如:
           類ClassA:

Java代碼
  1. public   class  ClassA {   
  2.          public   void  printOut() {   
  3.                 System.out.println( 0 );   
  4.         }   
  5. }  

 

           類ClassB:

 

Java代碼
  1. public   class  ClassB {   
  2.          public   void  printOut() {   
  3.                 System.out.println( 1 );   
  4.         }   
  5. }  
 

           子類ClassC:(假設允許類與類之間多繼承)

Java代碼
  1. public   class  ClassC  extends  ClassA, ClassB {   
  2.                     public   static   void  main(String[] args) {   
  3.                            ClassA classA =  new  ClassC();   
  4.                            classA.printOut();       // -------------------------  A行   
  5.                            ClassB classB =  new  ClassC();   
  6.                            classB.printOut();       // -------------------------  B行   
  7.                            ClassC classC =  new  ClassC();   
  8.                            classC.printOut();        //-------------------------  C行   
  9.                    }   
  10.            }  
 

           上面程序的運行結果會是什麼呢?A、B、C三行的輸出是0還是1?
正因爲有以上的致命缺點,所以java中禁止一個類繼承多個父類;但是幸運的是java提供了接口,並能通過接口的功能獲得多繼承的許多優點而又摒棄了類與類多繼承的缺點。
java允許一個接口繼承多個父接口,也允許一個類實現多個接口,而這樣的多繼承有上面提到的缺點馬?
答案是沒有,這是由接口的抽象性決定的。
正如前面介紹的,在接口中不能有實例變量,只能有靜態的常量,不能有具體的方法(包含方法體),只能有抽象方法,因此也就摒棄了多繼承的缺點。
對於一個類實現多個接口的情況,因爲接口只有抽象方法,具體方法只能由實現接口的類實現,在調用的時候始終只會調用實現類的方法(不存在歧義),因此不存在多繼承的第二個缺點;而又因爲接口只有靜態的常量,但是由於靜態變量是在編譯期決定調用關係的,即使存在一定的衝突也會在編譯時提示出錯;而引用靜態變量一般直接使用類名或接口名,從而避免產生歧義,因此也不存在多繼承的第一個缺點。
對於一個接口繼承多個父接口的情況也一樣不存在這些缺點。
請看以下示例。
            接口A:

Java代碼
  1. public   interface  InterfaceA {   
  2.          int  len =  1 ;   
  3.          void  output();   
  4. }  
 

            接口B:

Java代碼
  1. public   interface  InterfaceB {   
  2.            int  len =  2 ;   
  3.            void  output();   
  4. }  
 

            接口Sub繼承接口A和接口B:

Java代碼
  1. public   interface  InterfaceSub  extends  InterfaceA, interfaceB {            }  
 

            類Xyz實現接口Sub:

Java代碼
  1. public   class  Xyz  implements  InterfaceSub {   
  2.          public   void  output() {   
  3.                 System.out.println( "output in class Xyz." );   
  4.         }   
  5.           public   void  outputLen( int  type) {   
  6.                   switch (type) {   
  7.                           case  InterfaceA.len:   
  8.                                  System.out.println( "len of InterfaceA=." +type);   
  9.                                   break ;   
  10.                           case  InterfaceB.len:   
  11.                                  System.out.println( "len of InterfaceB=." +type);   
  12.                                   break ;   
  13.                  }   
  14.         }   
  15.         public   static   void  main(String[] args) {   
  16.                Xyz xyz=  new  Xyz ();   
  17.                xyz .output();   
  18.                xyz .outputLen();   
  19.        }   
 


           以上代碼不存在什麼問題,但是如果試圖編寫以下存在衝突的代碼,則會編譯失敗。

Java代碼
  1. Xyz xyz =  new  Xyz();   
  2. int  len = xyz.len;   
  3. System.out.println(len);  
 


由於引入了接口,java顯得非常靈活,也使得java中的多態性更加富有魔力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章