1. 程式人生 > >java 接口詳解

java 接口詳解

形式 接口繼承 比較 ima 繼承 calc 概念 動物 code

定義接口
接口繼承和實現繼承的規則不同,一個類只有一個直接父類,但可以實現多個接口。Java 接口本身沒有任何實現,只描述 public 行為,因此 Java 接口比 Java 抽象類更抽象化。Java 接口的方法只能是抽象的和公開的,Java 接口不能有構造方法,Java 接口可以有 public、Static 和 final 屬性。

接口把方法的特征和方法的實現分隔開來,這種分隔體現在接口常常代表一個角色,它包裝與該角色相關的操作和屬性,而實現這個接口的類便是扮演這個角色的演員。一個角色由不同的演員來演,而不同的演員之間除了扮演一個共同的角色之外,並不要求其他的共同之處。

接口對於其聲明、變量和方法都做了許多限制,這些限制作為接口的特征歸納如下:

具有 public 訪問控制符的接口,允許任何類使用;沒有指定 public 的接口,其訪問將局限於所屬的包。
方法的聲明不需要其他修飾符,在接口中聲明的方法,將隱式地聲明為公有的(public)和抽象的(abstract)。
在 Java 接口中聲明的變量其實都是常量,接口中的變量聲明,將隱式地聲明為 public、static 和 final,即常量,所以接口中定義的變量必須初始化。
接口沒有構造方法,不能被實例化。例如:

public interface A
{
    publicA(){…}    //編譯出錯,接口不允許定義構造方法
}
一個接口不能夠實現另一個接口,但它可以繼承多個其他接口。子接口可以對父接口的方法和常量進行重寫。例如:
public interface Student!nterface extends PeopleInterface
{
    //接口 StudentInterface 繼承 PeopleInterface
    int age=25;    //常量age重寫父接口中的age常量
    void getInfo();    //方法getInfo()重寫父接口中的getInfo()方法
}

Java 接口的定義方式與類基本相同,不過接口定義使用的關鍵字是 interface,接口定義由接口聲明和接口體兩部分組成。語法格式如下:
[public] interface interface_name [extends interface1_name[, interface2_name,…]]
{
    //接口體,其中可以包含定義常量和聲明方法
    [public] [static] [final] type constant_name=value;    //定義常量
    [public] [abstract] returnType method_name(parameter_list);    //聲明方法
}

其中,public 表示接口的修飾符,當沒有修飾符時,則使用默認的修飾符,此時該接口的訪問權限僅局限於所屬的包;interfaCe_name 表示接口的名稱,可以是任何有效的標識符;extends 表示接口的繼承關系;interface1_name 表示要繼承的接口名稱;constant_name 表示變量名稱,一般是 static 和 final 型的;returnType 表示方法的返回值類型;parameter_list 表示參數列表,在接口中的方法是沒有方法體的。

提示:如果接口本身被定義為 public,則所有的方法和常量都是 public 型的。

例如,定義一個接口 MyInterface,並在該接口中聲明常量和方法,如下:

public interface Mylnterface
{    //接口myInterface
    String name;    //不合法,變量name必須初始化
    int age=20;    //合法,等同於 public static final int age=20;
    void getInfo();    //方法聲明,等同於 public abstract void getInfo();
}

實現接口
接口被定義後,一個或者多個類都可以實現該接口,這需要在實現接口的類的定義中包含 implements 子句,然後實現由接口定義的方法。實現接口的一般形式如下:

<public> class <class_name> [extends superclass_name] [implements interface[, interface…]]
{
    //主體
}

如果一個類實現多個接口,這些接口需要使用逗號分隔。如果一個類實現兩個聲明了同樣方法的接口,那麽相同的方法將被其中任一個接口使用。實現接口的方法必須聲明為 public,而且實現方法的類型必須嚴格與接口定義中指定的類型相匹配。
例 1
在程序的開發中,需要完成兩個數的求和運算和比較運算功能的類非常多。那麽可以定義一個接口來將類似功能組織在一起。下面創建一個示例,具體介紹接口的實現方式。

(1) 創建一個名稱為 IMath 的接口,代碼如下:

public interface IMath
{
    public int sum();    //完成兩個數的相加
    public int maxNum(int a,int b);    //獲取較大的數
}

(2) 定義一個 MathClass 類並實現 IMath 接口,MathClass 類實現代碼如下:
public class MathClass implements IMath
{
    private int num1;    //第 1 個操作數
    private int num2;    //第 2 個操作數
    public MathClass(int num1,int num2)
    {
        //構造方法
        this.num1=num1;
        this.num2=num2;
    }
    //實現接口中的求和方法
    public int sum()
    {
        return num1+num2;
    }
    //實現接口中的獲取較大數的方法
    public int maxNum(int a,int b)
    {
        if(a>=b)
        {
            return a;
        }
        else
        {
            return b;
        }
    }
}

在實現類中,所有的方法都使用了 public 訪問修飾符聲明。無論何時實現一個由接口定義的方法,它都必須實現為 public,因為接口中的所有成員都顯式聲明為 public。

(3) 最後創建測試類 NumTest,實例化接口的實現類 MathClass,調用該類中的方法並輸出結果。該類內容如下:

public class NumTest
{
    public static void main(String[] args)
    {
        //創建實現類的對象
        MathClass calc=new MathClass(100, 300);
        System.out.println("100 和 300 相加結果是:"+calc.sum());
        System.out.println("100 比較 300,哪個大:"+calc.maxNum(100, 300));
    }
}

程序運行結果如下所示。
100 和 300 相加結果是:400
100 比較 300,哪個大:300

在該程序中,首先定義了一個 IMath 的接口,在該接口中只聲明了兩個未實現的方法,這兩個方法需要在接口的實現類中實現。
在實現類 MathClass 中定義了兩個私有的屬性,並賦予兩個屬性初始值,同時創建了該類的構造方法。
因為該類實現了 MathClass 接口,因此必須實現接口中的方法。在最後的測試類中,需要創建實現類對象,然後通過實現類對象調用實現類中的方法。

從前面對面向對象的設計原則的講解,讀者可以了解到,其實所有的設計原則和設計模式都離不開抽象,因為只有抽象才能實現上述設計原則和設計模式。

在 Java 中,針對抽象有兩種實現方式:一種是接口,一種是抽象類。很多讀者對這兩種實現方式比較困惑,到底是使用接口,還是使用抽象類呢?對於它們的選擇甚至反映出對問題領域本質的理解,對設計意圖的理解是否正確、合理?

在面向對象的設計思想中,所有的對象都是通過類來描繪的,但是反過來,並不是所有的類都是用來描繪對象的,如果一個類中沒有描繪一個具體的對象,那麽這樣的類就是抽象類,抽象類是對那些看上去不同,但是本質上相同的具體概念的抽象,正是因為抽象的概念在問題領域沒有對應的具體概念,所以抽象類是不能夠實例化的。
基本語法區別
在 Java 中,接口和抽象類的定義語法是不一樣的。這裏以動物類為例來說明,其中定義接口的示意代碼如下:

public interface Animal
{
    //所有動物都會吃
    public void eat();
    //所有動物都會飛
    public void fly();
}

定義抽象類的示意代碼如下:
public abstract class Animal
{
    //所有動物都會吃
    public abstract void eat();
    //所有動物都會飛
    public void fly(){};
}

可以看到,在接口內只能是功能的定義,而抽象類中則可以包括功能的定義和功能的實現。在接口中,所有的屬性肯定是 public、static 和 final,所有的方法都是 abstract,所以可以默認不寫上述標識符;在抽象類中,既可以包含抽象的定義,也可以包含具體的實現方法。

在具體的實現類上,接口和抽象類的實 現類定義方式也是不一樣的,其中接口實現類的示意代碼如下:

public class concreteAnimal implements Animal
{
    //所有動物都會吃
    public void eat(){}
    //所有動物都會飛
    public void fly(){}
}

抽象類的實現類示意代碼如下:
public class concreteAnimal extends Animal
{
    //所有動物都會吃
    public void eat(){}
    //所有動物都會飛
    public void fly(){}
}

可以看到,在接口的實現類中使用 implements 關鍵字;而在抽象類的實現類中,則使用 extends 關鍵字。一個接口的實現類可以實現多個接口,而一個抽象類的實現類則只能實現一個抽象類。
設計思想區別
從前面抽象類的具體實現類的實現方式可以看出,其實在 Java 中,抽象類和具體實現類之間是一種繼承關系,也就是說如果釆用抽象類的方式,則父類和子類在概念上應該是相同的。接口卻不一樣,如果采用接口的方式,則父類和子類在概念上不要求相同。接口只是抽取相互之間沒有關系的類的共同特征,而不用關註類之間的關系,它可以使沒有層次關系的類具有相同的行為。因此,可以這樣說:抽象類是對一組具有相同屬性和方法的邏輯上有關系的事物的一種抽象,而接口則是對一組具有相同屬性和方法的邏輯上不相關的事物的一種抽象。

仍然以前面動物類的設計為例來說明接口和抽象類關於設計思想的區別,該動物類默認所有的動物都具有吃的功能,其中定義接口的示意代碼如下:

public interface Animal
{
    //所有動物都會吃
    public void eat();
}

定義抽象類的示意代碼如下:
public abstract class Animal
{
    //所有動物都會吃
    public abstract void eat();
}

不管是實現接口,還是繼承抽象類的具體動物,都具有吃的功能,具體的動物類的示意代碼如下。

接口實現類的示意代碼如下:
public class concreteAnimal implements Animal
{
    //所有動物都會吃
    public void eat(){}
}

抽象類的實現類示意代碼如下:
public class concreteAnimal extends Animal
{
    //所有動物都會吃
    public void eat(){}
}

當然,具體的動物類不光具有吃的功能,比如有些動物還會飛,而有些動物卻會遊泳,那麽該如何設計這個抽象的動物類呢?可以別在接口和抽象類中增加飛的功能,其中定義接口的示意代碼如下:

public interface Animal
{
    //所有動物都會吃
    public void eat();
    //所有動物都會飛
    public void fly();
}

定義抽象類的示意代碼如下:
public abstract class Animal
{
    //所有動物都會吃
    public abstract void eat();

    //所有動物都會飛
    public void fly(){};
}

這樣一來,不管是接口還是抽象類的實現類,都具有飛的功能,這顯然不能滿足要求,因為只有一部分動物會飛,而會飛的卻不一定是動物,比如飛機也會飛。那該如何設計呢?有很多種方案,比如再設計一個動物的接口類,該接口具有飛的功能,示意代碼如下:

public interface AnimaiFly
{
    //所有動物都會飛
    public void fly();
}

那些具體的動物類,如果有飛的功能的話,除了實現吃的接口外,再實現飛的接口,示意代碼如下:

public class concreteAnimal implements Animal,AnimaiFly
{
    //所有動物都會吃
    public void eat(){}
    //動物會飛
    public void fly();
}

那些不需要飛的功能的具體動物類只實現具體吃的功能的接口即可。另外一種解決方案是再設計一個動物的抽象類,該抽象類具有飛的功能,示意代碼如下:

public abstract class AnimaiFly
{
    //動物會飛
    public void fly();
}

但此時沒有辦法實現那些既有吃的功能,又有飛的功能的具體動物類。因為在 Java 中具體的實現類只能實現一個抽象類。一個折中的解決辦法是,讓這個具有飛的功能的抽象類,繼承具有吃的功能的抽象類,示意代碼如下:

public abstract class AnimaiFly extends Animal
{
    //動物會飛
    public void fly();
}

此時,對那些只需要吃的功能的具體動物類來說,繼承 Animal 抽象類即可。對那些既有吃的功能又有飛的功能的具體動物類來說,則需要繼承 AnimalFly 抽象類。

但此時對客戶端有一個問題,那就是不能針對所有的動物類都使用 Animal 抽象類來進行編程,因為 Animal 抽象類不具有飛的功能,這不符合面向對象的設計原則,因此這種解決方案其實是行不通的。

還有另外一種解決方案,即具有吃的功能的抽象動物類用抽象類來實現,而具有飛的功能的類用接口實現;或者具有吃的功能的抽象動物類用接口來實現,而具有飛的功能的類用抽象類實現。

具有吃的功能的抽象動物類用抽象類來實現,示意代碼如下:

public abstract class Animal
{
    //所有動物都會吃
    public abstract void eat();
}

具有飛的功能的類用接口實現,示意代碼如下:
public interface AnimaiFly
{
    //動物會飛
    public void fly();
}

既具有吃的功能又具有飛的功能的具體的動物類,則繼承 Animal 動物抽象類,實現 AnimalFly 接口,示意代碼如下:

public class concreteAnimal extends Animal implements AnimaiFly
{
    //所有動物都會吃
    public void eat(){}
    //動物會飛
    public void fly();
}

或者具有吃的功能的抽象動物類用接口來實現,示意代碼如下:

public interface Animal
{
    //所有動物都會吃
    public abstract void eat();
}

具有飛的功能的類用抽象類實現,示意代碼如下:

public abstract class AnimaiFly
{
    //動物會飛
    public void fly(){};
}

既具有吃的功能又具有飛的功能的具體的動物類,則實現 Animal 動物類接口,繼承 AnimaiFly 抽象類,示意代碼如下:

public class concreteAnimal extends AnimaiFly implements Animal
{
    //所有動物都會吃
    public void eat(){}
    //動物會飛
    public void fly();
}

這些解決方案有什麽不同呢?再回過頭來看接口和抽象類的區別:抽象類是對一組具有相同屬性和方法的邏輯上有關系的事物的一種抽象,而接口則是對一組具有相同屬性和方法的邏輯上不相關的事物的一種抽象,因此抽象類表示的是“is a”關系,接口表示的是“like a”關系。

假設現在要研究的系統只是動物系統,如果設計人員認為對既具有吃的功能又具有飛的功能的具體的動物類來說,它和只具有吃的功能的動物一樣,都是動物,是一組邏輯上有關系的事物,因此這裏應該使用抽象類來抽象具有吃的功能的動物類,即繼承 Animal 動物抽象類,實現 AnimalFly 接口。

如果設計人員認為對既具有吃的功能,又具有飛的功能的具體的動物類來說,它和只具有飛的功能的動物一樣,都是動物,是一組邏輯上有關系的事物,因此這裏應該使用抽象類來抽象具有飛的功能的動物類,即實現 Animal 動物類接口,繼承 AnimaiFly 抽象類。

假設現在要研究的系統不只是動物系統,如果設計人員認為不管是吃的功能,還是飛的功能和動物類沒有什麽關系,因為飛機也會飛,人也會吃,則這裏應該實現兩個接口來分別抽象吃的功能和飛的功能,即除實現吃的 Animal 接口外,再實現飛的 AnimalFly 接口。

從上面的分析可以看出,對於接口和抽象類的選擇,反映出設計人員看待問題的不同角度,即抽象類用於一組相關的事物,表示的是“is a”的關系,而接口用於一組不相關的事物,表示的是“has a”的關系。

java 接口詳解