Java 接口
本小節(jié)我們將學(xué)習(xí) Java 接口(interface),通過本小節(jié)的學(xué)習(xí),你將了解到什么是接口、為什么需要接口、如何定義和實(shí)現(xiàn)接口,以及接口的特點(diǎn)等內(nèi)容。最后我們也將對(duì)比抽象類和接口的區(qū)別。
1. 概念
Java 接口是一系列方法的聲明,是一些方法特征的集合,一個(gè)接口只有方法的特征沒有方法的實(shí)現(xiàn)。
在 Java 中,被關(guān)鍵字 interface
修飾的 class 就是一個(gè)接口。接口定義了一個(gè)行為協(xié)議,可以由類層次結(jié)構(gòu)中任何位置的任何類實(shí)現(xiàn)。接口中定義了一組抽象方法,都沒有具體實(shí)現(xiàn),實(shí)現(xiàn)該接口的類必須實(shí)現(xiàn)該接口中定義的所有抽象方法。
2. 為什么需要接口
我們知道 Java 僅支持單繼承,也就是說一個(gè)類只允許有一個(gè)直接父類,這樣保證了數(shù)據(jù)的安全。Java 不支持下圖所示的多繼承:

接口就是為了解決 Java 單繼承這個(gè)弊端而產(chǎn)生的,雖然一個(gè)類只能有一個(gè)直接父類,但是它可以實(shí)現(xiàn)多個(gè)接口,沒有繼承關(guān)系的類也可以實(shí)現(xiàn)相同的接口。繼承和接口的雙重設(shè)計(jì)既保持了類的數(shù)據(jù)安全也變相實(shí)現(xiàn)了多繼承。
3. 接口的定義和實(shí)現(xiàn)
3.1 定義接口
3.1.1 接口聲明
使用 interface
關(guān)鍵字聲明一個(gè)接口:
public interface Person {
...
}
接口聲明需要兩個(gè)元素:interface
關(guān)鍵字和接口名稱,public
修飾符表示該接口可以在任何包的任何類中使用,如果為顯示指定訪問修飾符,則該接口只能被在同包中的類使用。
3.1.2 接口主體
接口主體中,可以定義常量和方法聲明:
public interface Person {
final String NAME = "我是Person接口中的常量";
void walk();
void run();
}
上面的 Person
就是一個(gè)接口,這個(gè)接口定義了一個(gè)常量 NAME
和兩個(gè)抽象方法 walk()
、run()
。
接口比抽象類更加 “抽象”,它下面不能擁有具體實(shí)現(xiàn)的方法,必須全部都是抽象方法,所有的方法默認(rèn)都是 public abstract
的,所以在接口主體中的方法,這兩個(gè)修飾符無需顯示指定。
接口除了方法聲明外,還可以包含常量聲明。在接口中定義的所有的常量默認(rèn)都是 public
,static
,和 final
的。
接口中的成員聲明不允許使用 private
和 protected
修飾符。
3.2 實(shí)現(xiàn)接口
接口定義了一些行為協(xié)議,而實(shí)現(xiàn)接口的類要遵循這些協(xié)議。implements
關(guān)鍵字用于實(shí)現(xiàn)接口,一個(gè)類可以實(shí)現(xiàn)一個(gè)或多個(gè)接口,當(dāng)要實(shí)現(xiàn)多個(gè)接口時(shí),implements
關(guān)鍵字后面是該類要實(shí)現(xiàn)的以逗號(hào)分割的接口名列表。其語法為:
public class MyClass implements MyInterface1, MyInterface2 {
...
}
下面是實(shí)現(xiàn)了 Person
接口的 Student
類的示例代碼:
public class Student implements Person {
@Override
public void walk() {
// 打印接口中的常量
System.out.println(Person.NAME);
System.out.println("學(xué)生可以走路");
}
@Override
public void run() {
System.out.println("學(xué)生可以跑步");
}
}
上述代碼中,Student
類實(shí)現(xiàn)了 Person
接口。值得注意的是,可以使用接口名。常量名的方式調(diào)用接口中所聲明的常量:
String name = Person.NAME;
4. 接口繼承
接口也是存在繼承關(guān)系的。接口繼承使用 extends
關(guān)鍵字。例如,聲明兩個(gè)接口 MyInterface1
和 MyInterface2
,MyInterface2
繼承自 MyInterface1
:
// MyInterface1.java
public interface MyInterface1 {
void abstractMethod1();
}
// MyInterface2.java
public interface MyInterface2 extends MyInterface1 {
void abstractMethod2();
}
當(dāng)一個(gè)類實(shí)現(xiàn) MyInterface2
接口,將會(huì)實(shí)現(xiàn)該接口所繼承的所有抽象方法:
// MyClass.java
public class MyClass implements MyInterface2 {
@Override
public void abstractMethod2() {
...
}
@Override
public void abstractMethod1() {
...
}
}
值得注意的是,一個(gè)接口可以繼承多個(gè)父接口,接口名放在 extends
后面,以逗號(hào)分割,例如:
// MyInterface1.java
public interface MyInterface1 {
void abstractMethod1();
}
// MyInterface2.java
public interface MyInterface2 {
void abstractMethod2();
}
// MyInterface3.java
public interface MyInterface3 extends MyInterface1, MyInterface2 {
void abstractMethod3();
}
補(bǔ)充一點(diǎn),當(dāng)一個(gè)實(shí)現(xiàn)類存在 extends
關(guān)鍵字,那么 implements
關(guān)鍵字應(yīng)該放在其后:
public class MyClass extends SuperClass implements MyInterface {
...
}
5. 默認(rèn)方法和靜態(tài)方法
從 JDK 1.8 開始,接口中可以定義默認(rèn)方法和靜態(tài)方法。與抽象方法不同,實(shí)現(xiàn)類可以不實(shí)現(xiàn)默認(rèn)方法和類方法。
5.1 默認(rèn)方法
5.1.1 聲明
我們可以使用 default
關(guān)鍵字,在接口主題中實(shí)現(xiàn)帶方法體的方法,例如:
public interface Person {
void run();
default void eat() {
System.out.println("我是默認(rèn)的吃方法");
}
}
5.1.2 調(diào)用和重寫
在實(shí)現(xiàn)類中,可以不實(shí)現(xiàn)默認(rèn)方法:
public class Student implements Person {
@Override
public void run() {
System.out.println("學(xué)生可以跑步");
}
}
我們也可以在實(shí)現(xiàn)類中重寫默認(rèn)方法,重寫不需要 default
關(guān)鍵字:
public class Student implements Person {
@Override
public void run() {
System.out.println("學(xué)生可以跑步");
}
// 重寫默認(rèn)方法
@Override
public void eat() {
// 使用 接口名.super.方法名() 的方式調(diào)用接口中默認(rèn)方法
Person.super.eat();
System.out.println("學(xué)生吃東西");
}
}
如果想要在實(shí)現(xiàn)類中調(diào)用接口的默認(rèn)方法,可以使用接口名.super. 方法名 () 的方式調(diào)用。這里的 接口名.super 就是接口的引用。
5.1.3 使用場(chǎng)景
當(dāng)一個(gè)方法不需要所有實(shí)現(xiàn)類都進(jìn)行實(shí)現(xiàn),可以在接口中聲明該方法為默認(rèn)方法;使用默認(rèn)方法還有一個(gè)好處,當(dāng)接口新增方法時(shí),將方法設(shè)定為默認(rèn)方法,只在需要實(shí)現(xiàn)該方法的類中重寫它,而不需要在所有實(shí)現(xiàn)類中實(shí)現(xiàn)。
5.2 靜態(tài)方法
5.2.1 聲明
使用 static
關(guān)鍵字在接口中聲明靜態(tài)方法,例如:
public interface Person {
void walk();
// 聲明靜態(tài)方法
static void sayHello() {
System.out.println("Hello imooc!");
}
}
5.2.2 調(diào)用
類中的靜態(tài)方法只能被子類繼承而不能被重寫,同樣在實(shí)現(xiàn)類中,靜態(tài)方法不能被重寫。如果想要調(diào)用接口中的靜態(tài)方法,只需使用 接口名。類方法名 的方式即可調(diào)用:
public class Student implements Person {
@Override
public void walk() {
// 調(diào)用接口中的類方法
Person.sayHello();
System.out.println("學(xué)生會(huì)走路");
}
}
6. 接口和抽象類的區(qū)別
- 接口的方法默認(rèn)是 public ,所有方法在接口中不能有實(shí)現(xiàn)(Java 8 開始接口方法可以有默認(rèn)實(shí)現(xiàn)),而抽象類可以有非抽象的方法;
- 接口中除了 static 、final 變量,不能有其他變量,而抽象類可以;
- 一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,但只能實(shí)現(xiàn)一個(gè)抽象類。接口自己本身可以通過 extends 關(guān)鍵字?jǐn)U展多個(gè)接口;
- 接口方法默認(rèn)修飾符是 public ,抽象方法可以有 public 、protected 和 default 這些修飾符(抽象方法就是為了被重寫所以不能使用 private 關(guān)鍵字修飾!);
- 從設(shè)計(jì)層面來說,抽象是對(duì)類的抽象,是一種模板設(shè)計(jì),而接口是對(duì)行為的抽象,是一種行為的規(guī)范。
7. 多個(gè)接口中的重名成員解決方法
7.1 多個(gè)接口存在重名默認(rèn)方法
例如有兩個(gè)接口 MyInteface1.java
和 MyInterface2.java
,存在相同簽名的默認(rèn)方法:
public interface MyInterface1 {
default void defaultMethod() {
System.out.println("我是MyInterface1接口中的默認(rèn)方法");
}
}
public interface MyInterface2 {
default void defaultMethod() {
System.out.println("我是MyInterface2接口中的默認(rèn)方法");
}
}
當(dāng)實(shí)現(xiàn)類實(shí)現(xiàn)兩個(gè)接口時(shí),同名的默認(rèn)方法將會(huì)發(fā)生沖突,解決辦法是在實(shí)現(xiàn)類中重寫這個(gè)默認(rèn)方法:
public class MyClass implements MyInterface1, MyInterface2 {
public void defaultMethod() {
System.out.println("我是重寫的默認(rèn)方法");
}
}
還有一種情況:實(shí)現(xiàn)類所繼承的父類中也存在與默認(rèn)方法的同名方法,此時(shí)存在三個(gè)同名方法:
// 聲明父類,并在父類中也定義同名方法
public class SuperClass {
public void defaultMethod() {
System.out.println("我是SuperClass中的defaultMethod()方法");
}
}
// 實(shí)現(xiàn)類繼承父類,并實(shí)現(xiàn)兩個(gè)接口
public class MyClass extends SuperClass implements MyInterface1, MyInterface2 {
}
實(shí)例化 MyClass
類,調(diào)用其 defaultMethod()
方法:
MyClass myClass = new MyClass();
myClass.defaultMethod();
此時(shí)編譯執(zhí)行,不會(huì)報(bào)錯(cuò):
我是SuperClass中的defaultMethod()方法
實(shí)際上,在沒有重寫的情況下,它執(zhí)行了實(shí)現(xiàn)類的父類 SuperClass
的 defaultMethod()
方法。
7.2 多個(gè)接口中存在重名常量
例如有兩個(gè)接口,存在重名的常量:
public interface MyInterface1 {
final int NUM = 100;
}
public interface MyInterface2 {
final int NUM = 200;
}
此時(shí)在實(shí)現(xiàn)類中,我們可以使用接口名。常量名的方式分別調(diào)用:
public MyClass implements MyInterface1, MyInterface2 {
System.out.println(MyInterface1.NUM);
System.out.println(MyInterface2.NUM);
}
當(dāng)實(shí)現(xiàn)類將入一個(gè)繼承關(guān)系時(shí):
class SuperClass {
static int NUM = 300;
}
public MyClass extends SuperClass implements MyInterface1, MyInterface2 {
System.out.println(NUM);
}
當(dāng)父類中的屬性或常量與接口中的常量同名時(shí),子類無法分辨同名的 NUM
是哪一個(gè)。編譯程序?qū)?huì)報(bào)錯(cuò):
MyClass.java:4: 錯(cuò)誤: 對(duì)NUM的引用不明確
System.out.println(NUM);
^
SuperClass 中的變量 NUM 和 MyInterface1 中的變量 NUM 都匹配
1 個(gè)錯(cuò)誤
此時(shí)只有在子類中聲明 NUM
,才可以通過編譯:
public MyClass extends SuperClass implements MyInterface1, MyInterface2 {
int NUM = 3;
System.out.println(NUM);
}
8. 小結(jié)
通過本小節(jié)的學(xué)習(xí),我們知道了 Java 的接口是為了解決其單繼承的弊端而產(chǎn)生的,可以使用 interface
關(guān)鍵字來聲明一個(gè)接口,接口內(nèi)部不能有具體的方法實(shí)現(xiàn)??梢允褂?implements
關(guān)鍵字來實(shí)現(xiàn)接口,一個(gè)接口可以繼承多個(gè)父接口,接口名放在 extends
后面,以逗號(hào)分割。從 Java 8 開始,接口中可以定義默認(rèn)方法和靜態(tài)方法。