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例項可以直接呼叫這兩個預設方法(由於介面中的預設方法為例項方法)。