抽象類是從多個類中抽象出來的模板,如果將這種抽象進行的更徹底,則可以提煉出一種更加特殊的“抽象類”————接口
從Java9開始,允許在接口中定義默認方法和類方法,默認方法和類方法都可以提供方法實現,還增加了一種私有方法,私有方法也可以提供方法實現。
接口的概念和作用
- 接口定義了某一批類所需要遵守的規範,接口不關心這些類的內部狀態數據,也不關心這些類裏方法的實現細節,它只規定這些類裏必須提供某些方法,提供這些方法的類就可以滿足實際需要
- 接口是從多個相似類中抽象出來的規範,接口不提供實現,它體現的是規範和實現分離
- 系統各模塊之間也應該採用這種面向接口的耦合,從而儘量降低各模塊之間的耦合,爲系統提供更好的可擴展性和可維護性
- 接口定義的是多個類共同的行爲規範,這些行爲是與外部交流的通道,這就意味着接口裏通常是定義一組方法
接口定義
[修飾符]interface 接口名 extends 父接口1, 父接口2...
{
零個到多個常量定義...
零個到多個抽象方法定義...
零個到多個內部類、接口、枚舉定義...
零個到多個私有方法、默認方法或者類方法定義...
}
- 修飾符可以是public也可以省略,如果省略則默認採用包權限訪問控制符,即只有在相同包結構下纔可以訪問該接口
- 接口名應與類名採用相同的命名規則,從語法角度來說,接口名只要是合法的標識符即可,但如果遵守Java的可讀性規範,則接口名應該由多個有意義的單詞連綴而成,每個單詞首字母大寫,單詞與單詞之間無需任何分隔符,而且可以使用形容詞
- 一個接口可以有多個直接父接口,但接口只能繼承接口不能繼承類
- Java8及以上才允許在接口中定義默認方法、類方法
- 接口定義的是一種規範,它不能包含構造器和初始化塊,可以包含成員變量(只能是靜態常量)、方法(只能是抽象方法、類方法、默認方法或私有方法)、內部類(包括內部接口、枚舉)
- 接口裏定義的是多個類共同的公共行爲規範,因此接口裏的常量、方法、內部類和內部枚舉都是public訪問權限,定義接口成員時可以省略訪問控制修飾符,如果指定訪問控制修飾符則只能用public
- Java9新增了一種方法叫私有方法,主要作用是作爲工具方法,爲接口中的默認方法和類方法提供支持,私有方法可以擁有方法體,但不能使用default修飾;私有方法可以使用static修飾,也就是說私有方法可以是類方法也可以是實例方法
- 對於接口的靜態常量而言,系統會自動爲這些成員變量增加static和final修飾符,也就是說接口定義成員變量時,不管是否使用public static final修飾,接口裏的成員變量總是使用這三個修飾符來修飾,而且接口沒有構造器和初始化塊,因此接口裏定義的成員變量只能在定義時指定默認值
- 接口裏定義的方法只能是抽象方法、類方法、默認方法或私有方法,如果不是定義默認方法、類方法或私有方法,系統將自動爲普通方法增加abstract修飾符,也就是說接口裏的普通方法不管是否顯示的使用了public abstract修飾符,系統都會給它加上當成抽象方法處理,因此普通方法也不能有方法體;但類方法、默認方法和私有方法必須有方法體
- 接口裏定義的內部類、內部接口、內部枚舉默認都採用public static兩個修飾符,不管定義時是否指定了兩個修飾符,系統都會自動使用public static對他們進行修飾
package davieyang;
public interface Output
{
// 接口裏定義的成員變量只能是常量
int MAX_CACHE_LINE = 50;
// 接口裏定義的普通方法只能是public的抽象方法
void out();
void getData(String msg);
// 在接口中定義默認方法,需要使用default修飾
default void print(String... msgs)
{
for (var msg : msgs)
{
System.out.println(msg);
}
}
// 在接口中定義默認方法,需要使用default修飾
default void test()
{
System.out.println("默認的test()方法");
}
// 在接口中定義類方法,需要使用static修飾
static String staticTest()
{
return "接口裏的類方法";
}
// 定義私有方法
private void foo()
{
System.out.println("foo私有方法");
}
// 定義私有靜態方法
private static void bar()
{
System.out.println("bar私有靜態方法");
}
}
- Java8允許接口定義默認方法,默認方法必須使用default修飾並且不能使用static
- 無論程序是否指定,默認方法總是使用public修飾,即便開發者沒有顯示的指定public,系統會自動爲默認方法添加public
- 由於默認方法不能用static,因此不能直接使用接口來調用默認方法,需要使用接口的實現類的實例來調用這些默認方法
- 接口的默認方法其實實例方法,在早起的java裏接口中的實例方法不能有方法體,在不破壞以前的規則情況下,增加了默認方法,默認方法就是有方法體的實例方法
- Java8還允許在接口中定義類方法,類方法必須使用static,且不能使用default修飾,無論程序是否指定,類方法總是使用public修飾,即便開發者沒有指定public,系統會自動爲類方法添加public修飾符,類方法可以直接使用接口來調用
- Java8之後接口可以定義這麼多種方法,難免會出現重複的實現邏輯,因此Java9新增了私有方法,用於將這些邏輯提煉出來成爲工具方法,而工具方法應該被隱藏。
- 接口裏的成員變量默認使用public static final修飾,因此即使類處於不同的package下,也可以通過接口來訪問接口裏的成員變量
package leadscloud;
public class OutputFieldTest
{
public static void main(String[] args)
{
// 訪問另一個包中的Output接口的MAX_CACHE_LINE
System.out.println(davieyang.Output.MAX_CACHE_LINE);
// 下面語句將引起"爲final變量賦值"的編譯異常
// davieyang.Output.MAX_CACHE_LINE = 20;
// 使用接口來調用類方法
System.out.println(davieyang.Output.staticTest());
}
}
從某個角度看,接口可以被當成特殊的類,因此一個Java源文件裏最多只能有一個public接口,如果一個Java源文件裏定義了一個public接口,則該源文件的主文件名必須與該接口名相同
接口的繼承
- 接口繼承和類的繼承不一樣,接口支持多繼承,即一個接口可以有多個直接父接口
- 和類繼承相似的是子接口擴展某個父接口,將會獲得父接口裏定義的所有抽象方法、常量
interface InterfaceA
{
int PROP_A = 5;
void testA();
}
interface InterfaceB
{
int PROP_B = 6;
void testB();
}
interface InterfaceC extends InterfaceA, InterfaceB
{
int PROP_C = 7;
void testC();
}
public class InterfaceExtendsTest
{
public static void main(String[] args)
{
// 通過InterfaceC來訪問繼承來的常量
System.out.println(InterfaceC.PROP_A);
System.out.println(InterfaceC.PROP_B);
System.out.println(InterfaceC.PROP_C);
}
}
使用接口
接口不能創建實例,但接口可以用於聲明引用類型變量,當使用接口來聲明引用類型變量時,這個引用類型變量必須引用到其實現類的對象,除此之外,接口的主要用途就是被實現類實現,歸納起來主要用途如下:
- 定義變量,也可用於進行強制類型轉換
- 調用接口中定義的常量
- 被其他類實現
實現接口
一個類可以實現一個或多個接口,繼承使用extends關鍵字,實現則使用implements關鍵字。
[修飾符]class 類名 extends 父類 implements 接口1,接口2....
{
類體部分
}
- 實現接口與繼承父類相似,一樣可以獲得所實現接口裏定義的常量(成員變量)、方法(包括抽象方法和默認方法)
- 一個類可以繼承一個父類,並同時實現多個接口,implements部分必須方法extends部分之後
- 一個類實現了一個或者多個接口之後,這個類必須完全實現這些接口裏定義的全部抽象方法(也就是重寫這些抽象方法),否則該類將保留從父接口那裏繼承到的抽象方法,該類也必須定義成抽象類。
- 一個類實現某個接口時,該類將會獲得接口中定義的常量(成員變量)、方法等,因此可以把實現接口理解爲一種特殊的繼承,相當於實現類繼承了一個徹底抽象的類(相當於除默認方法外,所有方法都是抽象方法的類)
import davieyang.Output;
// 定義一個接口
interface Product
{
int getProduceTime();
}
// 讓Printer類實現Output和Product接口
public class Printer implements Output, Product
{
private String[] printData = new String[MAX_CACHE_LINE];
// 用以記錄當前需打印的作業數
private int dataNum = 0;
public void out()
{
// 只要還有作業,繼續打印
while (dataNum > 0)
{
System.out.println("打印機打印:" + printData[0]);
// 把作業隊列整體前移一位,並將剩下的作業數減1
System.arraycopy(printData, 1, printData, 0, --dataNum);
}
}
public void getData(String msg)
{
if (dataNum >= MAX_CACHE_LINE)
{
System.out.println("輸出隊列已滿,添加失敗");
}
else
{
// 把打印數據添加到隊列裏,已保存數據的數量加1。
printData[dataNum++] = msg;
}
}
public int getProduceTime()
{
return 45;
}
public static void main(String[] args)
{
// 創建一個Printer對象,當成Output使用
Output o = new Printer();
o.getData("輕量級Java EE企業應用實戰");
o.getData("瘋狂Java講義");
o.out();
o.getData("瘋狂Android講義");
o.getData("瘋狂Ajax講義");
o.out();
// 調用Output接口中定義的默認方法
o.print("孫悟空", "豬八戒", "白骨精");
o.test();
// 創建一個Printer對象,當成Product使用
Product p = new Printer();
System.out.println(p.getProduceTime());
// 所有接口類型的引用變量都可直接賦給Object類型的變量
Object obj = p;
}
}
- Printer類實現了Output接口和Product接口,因此Printer對象既可以直接賦給Output變量,也可以直接賦給Product變量,彷彿Printer類既是Output類的子類,也是Product類的子類,這就是Java提供的模擬多繼承
- 程序中Printer實現了Output接口,即可獲得Output接口中定義的print()和test()兩個默認方法,因此Printer實例可以直接調用這兩個默認方法
- 實現接口方法時,必須使用public訪問控制修飾符,因爲接口裏的方法都是public的,而子類(相當於實現類)重寫父類方法時訪問權限只能更大或者相當,所以實現類實現接口裏的方法時只能使用public訪問權限
- 接口不能顯示的繼承任何類,但所有接口類型的應用變量都可以直接賦給Object類型的引用變量,因此在上面的程序中可以把Product類型的變量直接賦給Object類型變量,這也是利用向上轉型來實現的,編譯器知道任何Java對象都必須是Object或其子類的實例,Product類型的對象也不例外(它必須是Product接口實現類的對象,該實現類肯定是Object的顯示或隱式子類)
接口和抽象類的聯繫與區別
接口和抽象類共同特徵
- 接口和抽象類都不能被實例化,他們都位於繼承樹的頂端,用於被其他類實現和繼承
- 接口和抽象類都可以包含抽象方法,實現接口或繼承抽象類的普通子類都必須實現這些抽象方法
接口和抽象類區別
意義上的區別
- 接口體現的是一種規範,對於接口的實現者而言,接口規定了實現者必須向外提供哪些服務(以方法的形式來提供);對於接口的調用者而言,接口規定了調用者可以調用哪些服務,以及如何調用這些服務(就是如何來調用方法);
- 當在一個程序中使用接口時,接口是多個模塊間的偶和標準;當在多個應用程序之間使用接口時,接口是多個程序之間的通信標準
- 接口類似於整個系統的“總綱”,制訂了系統各模塊應該遵循的標準,因此一個系統中的接口不應該經常改變,對整個系統甚至其他系統將是輻射式的,導致系統中大部分類都需要改寫
- 抽象類則不一樣,它作爲系統中多個子類的共同父類,它所體現的是一種模板式設計,抽象類作爲多個子類的抽象父類,可以被當成系統實現過程中的中間產品,這個中間產品已經實現了系統的部分功能(那些已經提供實現的方法),但這個中間產品需要進一步完善
用法上的區別
- 接口裏只能包含抽象方法、靜態方法、默認方法和私有方法,不能爲普通方法提供方法實現;抽象類則安全可以包含普通方法
- 接口裏只能定義靜態常量,不能定義普通成員變量;抽象類裏則既可以定義普通成員變量,也可以定義靜態常量
- 接口裏不包含構造器;抽象類裏可以包含構造器,但抽象類的構造器不是用來實例化,而是用來讓其子類調用這些構造器來完成屬於抽象類的初始化操作
- 接口不能包含初始化塊;但抽象類則完全可以包含初始化塊
- 一個類最多只能有一個直接父類,包括抽象類;但一個類可以直接實現多個接口,通過實現多個接口可以彌補Java的單繼承不足