1. 程式人生 > >Java面向物件(三)

Java面向物件(三)

1、介面

1.1、概述

抽象類是從多個類中抽象出來的模板,如果將這種抽象進行得更徹底,則可以提煉出一種更加特殊的“抽象類”——介面(inteface)。 Java 9對介面進行了改進,允許在介面中定義預設方法和類方法,預設方法和類方法都可以提供方法實現,Java 9為介面增加了一種私有方法,私有方法也可提供方法實現。 我們可能經常聽說介面,比如PCI介面、AGP介面等,因此很多人認為介面等同於主機板上的插槽,這其實一種錯誤的認識,當說PCI介面時,指的是主機板上那個插槽遵守了PCI規範,而具體的插槽只是PCI介面的例項。對於不同型號的主機板而言,他們各自的PCI插槽都需要遵守一個規範,遵守這個規範就可以保證插入該插槽裡的板卡能與主機板正常通訊。 介面是從多個相似類中抽象出來的規範,介面不提供任何實現,介面體現的是規範和實現分離的設計哲學。例如主機板上提供了PCI插槽,只要一塊顯示卡遵守PCI介面規範,就可以插入PCI插槽內,與該主機板正常通訊。至於這塊顯示卡是哪家制造的,內部如何實現的,主機板無須關心。 因此,介面定義的是多個類共同的公共行為規範,這些行為是與外部交流的通道,這就意味著接口裡通常是定義一組公用方法。

  • 接口裡可以包含:
    • 成員變數:只能是靜態常量
    • 方法:只能是抽象例項方法、類方法、預設方法、私有方法(Java 9新增)
    • 內部類:包括內部介面、列舉。

1.2、介面的定義

介面的定義如下所示:

修飾符 interface 介面名 extends 父介面1,父介面2... {
    //零到多個常量定義...
    //零到多個抽象方法定義...
    //零到多個預設方法、類方法、私有方法定義...
    //零到多個內部類、介面、列舉類定義...
}

針對上面的語法作如下說明:

  • 修飾符可以是public或者省略,若省略public,則預設採用包許可權訪問控制符(即只有在相同包結構下才可以訪問該介面)。
  • 介面名應與類名採用相同的命名規範;
  • 一個介面可以有多個直接的父介面,但介面只能繼承介面,不能繼承類。
  • 只有在Java 8以上的版本中才可以定義預設方法、類方法及私有方法(Java 9新增)。

與類相比,接口裡的成員變數只能是靜態常量,接口裡的方法只能是抽象方法、類方法、預設方法及私有方法。

下面定義一個介面。

interface Output {
    int MAX_CACHE_LINE = 50;

    void out();//輸出方法

    void getData(String msg);//取得資料的方法

    default void print(String... msgs)
{ for (String msg : msgs) { System.out.println(msg); } } default void test() { System.out.println("預設的test()方法"); } static String staticTest() { return "接口裡的類方法"; } }

上面定義了一個OutPut介面,這個接口裡包含了一個成員變數:MAX_CACHE_LINE。除此之外,這個介面還定義了兩個普通的方法:表示取得資料的getData()方法和表示輸出的out()方法。這就定義了Output介面的規範:只要某個類能取得資料,並可以將資料輸出,那它就是一個輸出裝置,置於這個裝置的實現細節,這裡暫時不關心。

  • 介面定義的是多個類共同的公共行為規範,因此接口裡的常量、方法、內部類和內部列舉都是public訪問許可權,若省略則預設為public許可權,若使用則只能是public修飾符。
  • 介面中定義的靜態常量,不管是否使用修飾符,省略時,預設會加上public static final修飾。
  • 接口裡若不是定義的預設方法、類方法或私有方法,則系統自動為普通方法增加abstract修飾符,即普通的方法總是使用public abstract修飾。
  • 接口裡的普通方法不能有方法實現(即方法體),但類方法、預設方法、私有方法都必須由方法實現。
  • 私有方法的主要作用是作為工具方法,為介面中的預設方法或類方法提供支援。私有方法既可以是類方法,也可以是例項方法。
  • 介面中的內部類、內部介面、內部列舉預設都採用public static修飾。
  • 介面中預設方法採用default進行修飾,總是被public進行修飾。介面中的預設方法就是例項方法。
  • 介面支援多繼承,而類只能是單繼承,因此介面可以有多個直接的父類。子介面繼承某個父介面,將會獲得父接口裡定義的所有抽象方法、常量。

下面我們寫一個測試類,呼叫一下接口裡的成員變數和類方法。

public class OutputFieldTest {
    public static void main(String[] args) {
        //使用介面呼叫靜態常量
        System.out.println(Output.MAX_CACHE_LINE);
        //接口裡定義的是靜態常量(預設自帶public static final),由於被final修飾,不允許被修改,以下語句會引發編譯錯誤
        //Output.MAX_CACHE_LINE=20;
        //使用介面呼叫類方法
        System.out.println(Output.staticTest());
    }
}

結合上面的程式碼,從某個角度來看,介面可被當成一個特殊的類。

1.3、介面的繼承

看下面的例子

interface InterfaceA {
    int PROP_A = 1;
    void testA();
}

interface InterfaceB {
    int PROP_B = 2;
    void testB();
}

interface InterfaceC extends InterfaceA, InterfaceB {
    int PROP_C = 3;
    void testC();
}

public class InterfaceExtendsTest {
    public static void main(String[] args) {
        System.out.println(InterfaceC.PROP_A);
        System.out.println(InterfaceC.PROP_B);
        System.out.println(InterfaceC.PROP_C);
    }
}

上面程式中介面InterfaceC通過繼承InterfaceA和InterfaceB,並且獲得了它們的常量。 在這裡插入圖片描述

1.4、介面的使用

介面不能用於建立例項,但介面可以用於宣告引用型別變數。當使用介面來宣告引用型別的變數時,這個引用型別變數必須引用到其實現類的物件。除此之外,介面主要用途就是被實現類實現。總結一下介面的用途(類實現介面的語法這裡就省略了):

  • 定義變數,也可用於進行強制型別轉換;
  • 呼叫介面中定義的常量;
  • 被其他類實現。 下面再定義一個Product介面,並定義一個Printer類,讓其實現Product和Output介面。
interface Product {
    int getProduceTime();
}

public class Printer implements Product, Output {
    //通過介面中靜態常量來初始化陣列
    private String[] printData = new String[MAX_CACHE_LINE];

    private int dataNum = 0;//用以記錄當前需列印的作業數

    @Override
    public void out() {
        //只要還有作業就繼續列印
        while (dataNum > 0) {
            System.out.println("印表機列印:" + printData[0]);
            //把作業佇列整體前移一位
            System.arraycopy(printData, 1, printData, 0, --dataNum);
        }
    }

    @Override
    public void getData(String msg) {
        if (dataNum >= MAX_CACHE_LINE) {
            System.out.println("輸出佇列已滿,新增失敗!");
        } else {
            printData[dataNum++] = msg;//將列印資料新增到佇列裡,已儲存的資料數量加一
        }
    }

    @Override
    public int getProduceTime() {
        return 45;
    }

    public static void main(String[] args) {
        //建立一個Printer物件,當成Output使用
        Output o = new Printer();
        o.getData("JavaEE實戰");
        o.getData("瘋狂Java講義");
        o.out();
        o.getData("瘋狂Android講義");
        o.getData("瘋狂Kotlin講義");
        o.out();

        o.print("唐僧", "孫悟空", "豬八戒");//呼叫Output介面中的預設方法

        o.test();

        //建立一個Printer物件,當成Product使用
        Product p = new Printer();
        System.out.println(p.getProduceTime());

        Object obj = p;//所有介面型別的引用變數都可直接賦給Object型別變數
    }
}

上面的程式中Printer類實現了Product和Output介面,因此Printer物件皆可以賦給Output變數,又可以賦給Product變數。看著好像Printer類既是Output的子類,有事Product的子類,這就是Java提供的模擬多繼承。而Printer由於實現了Output介面,則可以獲取介面中定義的print()和test()兩個預設方法,則Printer例項可以直接呼叫這兩個預設方法(由於介面中的預設方法為例項方法)。

2、面向介面程式設計