天天看點

Java 抽象類與接口

接口和内部類為我們提供了一種将接口與實作分離的更加結構化的方法。

抽象類與接口是 Java 語言中對抽象概念進行定義的兩種機制,正是由于他們的存在才賦予 Java 強大的面向對象的能力。他們兩者之間對抽象概念的支援有很大的相似,甚至可以互換,但是也有差別。

一、抽象類

我們都知道在面向對象的領域一切都是對象,同時所有的對象都是通過類來描述的,但是并不是所有的類都是來描述對象的。如果一個類沒有足夠的資訊來描述一個具體的對象,而需要其他具體的類來支撐它,那麼這樣的類我們稱它為抽象類。比如 new Animal(),我們都知道這個是産生一個動物 Animal 對象,但是這個 Animal 具體長成什麼樣子我們并不知道,它沒有一個具體動物的概念,是以他就是一個抽象類,需要一個具體的動物,如狗、貓來對它進行特定的描述,我們才知道它長成啥樣。

在面向對象領域由于抽象的概念在問題領域沒有對應的具體概念,是以用以表征抽象概念的抽象類是不能執行個體化的。

同時,抽象類展現了資料抽象的思想,是實作多态的一種機制。它定義了一組抽象的方法,至于這組抽象方法的具體表現形式有派生類來實作。同時抽象類提供了繼承的概念,它的出發點就是為了繼承,否則它沒有存在的任何意義。是以說定義的抽象類一定是用來繼承的,同時在一個以抽象類為節點的繼承關系等級鍊中,葉子節點一定是具體的實作類。(不知這樣了解是否有錯!!!高手指點….)

在使用抽象類時需要注意幾點:

1、抽象類不能被執行個體化,執行個體化的工作應該交由它的子類來完成,它隻需要有一個引用即可。

2、抽象方法必須由子類來進行重寫。

3、隻要包含一個抽象方法的抽象類,該方法必須要定義成抽象類,不管是否還包含有其他方法。

4、抽象類中可以包含具體的方法,當然也可以不包含抽象方法。

5、子類中的抽象方法不能與父類的抽象方法同名。

6、abstract 不能與 final 并列修飾同一個類。

7、abstract 不能與 private、static、final 或 native 并列修飾同一個方法。

執行個體:

定義一個抽象動物類 Animal,提供抽象方法叫 cry(),貓、狗都是動物類的子類,由于 cry() 為抽象方法,是以 Cat、Dog 必須要實作 cry() 方法。如下:

public abstract class Animal {
        public abstract void cry();
    }

    public class Cat extends Animal{

        @Override
        public void cry() {
            System.out.println("貓叫:喵喵...");
        }
    }

    public class Dog extends Animal{

        @Override
        public void cry() {
            System.out.println("狗叫:汪汪...");
        }

    }

    public class Test {

        public static void main(String[] args) {
            Animal a1 = new Cat();
            Animal a2 = new Dog();

            a1.cry();
            a2.cry();
        }
    }

    --------------------------------------------------------------------
    Output:
    貓叫:喵喵...
    狗叫:汪汪...      
建立抽象類和抽象方法非常有用,因為他們可以使類的抽象性明确起來,并告訴使用者和編譯器打算怎樣使用他們.抽象類還是有用的重構器,因為它們使我們可以很容易地将公共方法沿着繼承層次結構向上移動。(From:Think in Java )      

二、接口

接口是一種比抽象類更加抽象的“類”。這裡給“類”加引号是我找不到更好的詞來表示,但是我們要明确一點就是,接口本身就不是類,從我們不能執行個體化一個接口就可以看出。如 new Runnable();肯定是錯誤的,我們隻能 new 它的實作類。

接口是用來建立類與類之間的協定,它所提供的隻是一種形式,而沒有具體的實作。同時實作該接口的實作類必須要實作該接口的所有方法,通過使用 implements 關鍵字,他表示該類在遵循某個或某組特定的接口,同時也表示着"interface"隻是它的外貌,但是現在需要聲明它是如何工作的”。

接口是抽象類的延伸,java 了保證資料安全是不能多重繼承的,也就是說繼承隻能存在一個父類,但是接口不同,一個類可以同時實作多個接口,不管這些接口之間有沒有關系,是以接口彌補了抽象類不能多重繼承的缺陷,但是推薦繼承和接口共同使用,因為這樣既可以保證資料安全性又可以實作多重繼承。

在使用接口過程中需要注意如下幾個問題:

1、1個 Interface 的方所有法通路權限自動被聲明為 public。确切的說隻能為 public,當然你可以顯示的聲明為 protected、private,但是編譯會出錯!

2、接口中可以定義“成員變量”,或者說是不可變的常量,因為接口中的“成員變量”會自動變為為 public static final。可以通過類命名直接通路:ImplementClass.name。

3、接口中不存在實作的方法。

4、實作接口的非抽象類必須要實作該接口的所有方法。抽象類可以不用實作。

5、不能使用 new 操作符執行個體化一個接口,但可以聲明一個接口變量,該變量必須引用 (refer to) 一個實作該接口的類的對象。可以使用 instanceof 檢查一個對象是否實作了某個特定的接口。例如:if(anObject instanceof Comparable){}。

6、在實作多接口的時候一定要避免方法名的重複。

三、抽象類與接口的差別

盡管抽象類和接口之間存在較大的相同點,甚至有時候還可以互換,但這樣并不能彌補他們之間的差異之處。下面将從文法層次和設計層次兩個方面對抽象類和接口進行闡述。

3.1文法層次

在文法層次,java 語言對于抽象類和接口分别給出了不同的定義。下面已 Demo 類來說明他們之間的不同之處。

使用抽象類來實作:
    public abstract class Demo {
        abstract void method1();

        void method2(){
            //實作
        }
    }
使用接口來實作
    interface Demo {
        void method1();
        void method2();
    }      

抽象類方式中,抽象類可以擁有任意範圍的成員資料,同時也可以擁有自己的非抽象方法,但是接口方式中,它僅能夠有靜态、不能修改的成員資料(但是我們一般是不會在接口中使用成員資料),同時它所有的方法都必須是抽象的。在某種程度上來說,接口是抽象類的特殊化。

對子類而言,它隻能繼承一個抽象類(這是 java 為了資料安全而考慮的),但是卻可以實作多個接口。

3.2設計層次

上面隻是從文法層次和程式設計角度來區分它們之間的關系,這些都是低層次的,要真正使用好抽象類和接口,我們就必須要從較高層次來區分了。隻有從設計理念的角度才能看出它們的本質所在。一般來說他們存在如下三個不同點:

1、 抽象層次不同。抽象類是對類抽象,而接口是對行為的抽象。抽象類是對整個類整體進行抽象,包括屬性、行為,但是接口卻是對類局部(行為)進行抽象。

2、 跨域不同。抽象類所跨域的是具有相似特點的類,而接口卻可以跨域不同的類。我們知道抽象類是從子類中發現公共部分,然後泛化成抽象類,子類繼承該父類即可,但是接口不同。實作它的子類可以不存在任何關系,共同之處。例如貓、狗可以抽象成一個動物類抽象類,具備叫的方法。鳥、飛機可以實作飛 Fly 接口,具備飛的行為,這裡我們總不能将鳥、飛機共用一個父類吧!是以說抽象類所展現的是一種繼承關系,要想使得繼承關系合理,父類和派生類之間必須存在"is-a"關系,即父類和派生類在概念本質上應該是相同的。對于接口則不然,并不要求接口的實作者和接口定義在概念本質上是一緻的, 僅僅是實作了接口定義的契約而已。

3、 設計層次不同。對于抽象類而言,它是自下而上來設計的,我們要先知道子類才能抽象出父類,而接口則不同,它根本就不需要知道子類的存在,隻需要定義一個規則即可,至于什麼子類、什麼時候怎麼實作它一概不知。比如我們隻有一個貓類在這裡,如果你這是就抽象成一個動物類,是不是設計有點兒過度?我們起碼要有兩個動物類,貓、狗在這裡,我們在抽象他們的共同點形成動物抽象類吧!是以說抽象類往往都是通過重構而來的!但是接口就不同,比如說飛,我們根本就不知道會有什麼東西來實作這個飛接口,怎麼實作也不得而知,我們要做的就是事前定義好飛的行為接口。是以說抽象類是自底向上抽象而來的,接口是自頂向下設計出來的。

(上面純屬個人見解,如有出入、錯誤之處,望各位指點!!!!)

為了更好的闡述他們之間的差別,下面将使用一個例子來說明。該例子引自:http://blog.csdn.net/ttgjz/article/details/2960451

我們有一個 Door 的抽象概念,它具備兩個行為 open() 和 close(),此時我們可以定義通過抽象類和接口來定義這個抽象概念:

抽象類:

abstract class Door{
        abstract void open();
        abstract void close();
    }
接口


    interface Door{
        void open();
        void close();
    }      

至于其他的具體類可以通過使用 extends 使用抽象類方式定義 Door 或者 Implements 使用接口方式定義 Door,這裡發現兩者并沒有什麼很大的差異。

但是現在如果我們需要門具有報警的功能,那麼該如何實作呢?

解決方案一:給 Door 增加一個報警方法:clarm();

abstract class Door{
        abstract void open();
        abstract void close();
        abstract void alarm();
    }
或者


    interface Door{
        void open();
        void close();
        void alarm();
    }       

這種方法違反了面向對象設計中的一個核心原則 ISP (Interface Segregation Principle)—見批注,在 Door 的定義中把 Door 概念本身固有的行為方法和另外一個概念"報警器"的行為方 法混在了一起。這樣引起的一個問題是那些僅僅依賴于 Door 這個概念的子產品會因為”報警器”這個概念的改變而改變,反之依然。

解決方案二

既然 open()、close() 和alarm() 屬于兩個不同的概念,那麼我們依據 ISP 原則将它們分開定義在兩個代表兩個不同概念的抽象類裡面,定義的方式有三種:

1、兩個都使用抽象類來定義。

2、兩個都使用接口來定義。

3、一個使用抽象類定義,一個是用接口定義。

由于 Java 不支援多繼承是以第一種是不可行的。後面兩種都是可行的,但是選擇何種就反映了你對問題域本質的了解。

如果選擇第二種都是接口來定義,那麼就反映了兩個問題:1、我們可能沒有了解清楚問題域,AlarmDoor 在概念本質上到底是門還報警器。2、如果我們對問題域的了解沒有問題,比如我們在分析時确定了 AlarmDoor 在本質上概念是一緻的,那麼我們在設計時就沒有正确的反映出我們的設計意圖。因為你使用了兩個接口來進行定義,他們概念的定義并不能夠反映上述含義。

第三種,如果我們對問題域的了解是這樣的:AlarmDoor 本質上 Door,但同時它也擁有報警的行為功能,這個時候我們使用第三種方案恰好可以闡述我們的設計意圖。AlarmDoor 本質是們,是以對于這個概念我們使用抽象類來定義,同時 AlarmDoor 具備報警功能,說明它能夠完成報警概念中定義的行為功能,是以 alarm 可以使用接口來進行定義。如下:

abstract class Door{
        abstract void open();
        abstract void close();
    }

    interface Alarm{
        void alarm();
    }

    class AlarmDoor extends Door implements Alarm{
        void open(){}
        void close(){}
        void alarm(){}
    }      

這種實作方式基本上能夠明确的反映出我們對于問題領域的了解,正确的揭示我們的設計意圖。其實抽象類表示的是"is-a"關系,接口表示的是"like-a"關系,大家在選擇時可以作為一個依據,當然這是建立在對問題領域的了解上的,比如:如果我們認為 AlarmDoor 在概念本質上是報警器,同時又具有 Door 的功能,那麼上述的定義方式就要反過來了。

批注: ISP(Interface Segregation Principle)`:面向對象的一個核心原則。它表明使用多個專門的接口比使用單一的總接口要好。

一個類對另外一個類的依賴性應當是建立在最小的接口上的。

一個接口代表一個角色,不應當将不同的角色都交給一個接口。沒有關系的接口合并在一起,形成一個臃腫的大接口,這是對角色和接口的污染。

四、總結

1、抽象類在 Java 語言中所表示的是一種繼承關系,一個子類隻能存在一個父類,但是可以存在多個接口。

2、在抽象類中可以擁有自己的成員變量和非抽象類方法,但是接口中隻能存在靜态的不可變的成員資料(不過一般都不在接口中定義成員資料),而且它的所有方法都是抽象的。

3、抽象類和接口所反映的設計理念是不同的,抽象類所代表的是“is-a”的關系,而接口所代表的是“like-a”的關系。

抽象類和接口是 Java 語言中兩種不同的抽象概念,他們的存在對多态提供了非常好的支援,雖然他們之間存在很大的相似性。但是對于他們的選擇往往反應了您對問題域的了解。隻有對問題域的本質有良好的了解,才能做出正确、合理的設計。

熬夜不易,點選請老王喝杯烈酒!!!!!!!