介面的由來
兔子不會游泳,游泳這個方法放在動物類中,顯然是不合適的。而貓和狗都有游泳這個方法,那麼如果貓和狗各自寫自己的游泳方法,則可能導致這個方法不統一。
於是就可以定義一個介面,在其中規定了游泳這個方法的書寫規則,讓貓和狗各自去實現。
而看家這個方法又是狗這個類所特有的,就不要放進介面中。
所以介面放的就是部分類公共的方法,給這些方法規定一個統一的規則。
介面定義的不是繼承的規則,只是一個功能,或者說是一種規則,是對行為的抽象。
介面的定義和形式
介面用關鍵字 interface 來定義:public interface 介面名 {}
介面不能例項化。
介面和類之間是實現關係,透過 implements 關鍵字來表示:public class 類名 implements 介面名 {}
介面的子類也叫實現類。實現類要麼重寫介面中的所有抽象方法,要麼實現類本身也是一個抽象類。一般都是重寫介面中的所有抽象方法。
介面和類之間的實現關係,可以是單實現,也可以是多實現。
多實現:public class 類名 implements 介面名 1, 介面名 2 {}
實現類可以在繼承一個類的同時實現多個介面:public class 類名 extends 父類 implements 介面名 1, 介面名 2 {}
新建介面檔案:
如果新建介面檔案時,忘了選擇 interface 選項,可以在建立完檔案後將 class 改為 interface,檔案圖示也會跟著改過來。
練習:
編寫帶有介面和抽象類的標準 Javabean 類
青蛙 屬性:名字,年齡 行為:吃蟲子,蛙泳
狗 屬性:名字,年齡 行為:吃骨頭,狗刨
兔子 屬性:名字,年齡 行為:吃胡蘿蔔
Javabean 類:
package demo;
public abstract class Animal {
String name;
int age;
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 三個子類都有吃這個方法,而具體的方法體不同,則將吃這個方法抽取到父類中,且定義為抽象類,強制子類重寫
public abstract void eat();
}
介面:
package demo;
public interface Swim {
public abstract void swim();
}
Javabean 類:
package demo;
public class Dog extends Animal implements Swim {
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("狗吃骨頭。");
}
@Override
public void swim() {
System.out.println("狗在狗刨。");
}
}
Javabean 類:
package demo;
public class Frog extends Animal implements Swim {
public Frog() {
}
public Frog(String name, int age) {
super(name, age);
}
@Override
public void swim() {
System.out.println("青蛙在蛙泳。");
}
@Override
public void eat() {
System.out.println("青蛙在吃蟲子。");
}
}
Javabean 類:
package demo;
public class Rabbit extends Animal {
public Rabbit() {
}
public Rabbit(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("兔子在吃胡蘿蔔。");
}
}
測試類:
package demo;
public class Test {
public static void main(String[] args) {
Frog f = new Frog("小青", 23);
System.out.println(f.getName() + ", " + f.getAge());
f.eat();
f.swim();
Rabbit r = new Rabbit("小白", 21);
System.out.println(r.getName() + ", " + r.getAge());
r.eat();
// r.swim();
}
}
執行結果:
小青, 23
青蛙在吃蟲子。
青蛙在蛙泳。
小白, 21
兔子在吃胡蘿蔔。
介面中成員的特點
成員變數
只能是常量。
預設修飾符:public static final。預設指的是就算不寫,也預設是存在的。介面是一種規則,規則是不能被改變的,所以介面裡的成員變數都是常量,所以用 final 修飾。用 static 來修飾是為了方便呼叫,用 介面名.常量名
就可以呼叫。public 表示公共的,即所有的地方都可以使用介面裡的常量。
構造方法
沒有構造方法。因為介面不能建立物件,並且介面也不需要給子類的成員變數賦值。
成員方法
在 JDK 7 之前,只能寫抽象方法,預設修飾符為 public abstract,即哪怕不寫虛擬機器也會自動幫你加上。
JDK 8:介面中可以定義有方法體的方法。
JDK 9:介面中可以定義私有方法。
程式示例:
介面:
package demo3;
public interface Inter {
int a = 10;
}
測試類:
package demo3;
public class Test {
public static void main(String[] args) {
System.out.println(Inter.a); // 列印 10,此處是用 介面名.變數名 的形式來呼叫的,由此可以說明是用 static 來修飾的。
// Inter.a = 20; // 報錯:cannot assign a value to final variable 'a' 由此說明是用 final 修飾的。
}
}
介面和類之間的關係:
類和類的關係:繼承關係,只能單繼承,不能多繼承,但是可以多層繼承
類和介面的關係:實現關係,可以單實現,也可以多實現,還可以在繼承一個類的同時實現多個介面. 如果一個類實現了多個介面, 那麼就要重寫全部的抽象方法.
介面和介面的關係:繼承關係,可以單繼承,也可以多繼承。如果實現類實現了最下面的子介面的話,那麼就需要重寫所有的抽象方法。
如果實現多個介面時,多個介面有相同的方法,那麼在子類中只需要重寫一次重複的方法。
介面 1:
public interface Inter1 {
public abstract void method1();
public abstract void method_abc();
}
介面 2:
public interface Inter2 {
public abstract void method2();
public abstract void method_abc();
}
介面 3:
public interface Inter3 extends Inter2, Inter1 {
public abstract void method3();
}
實現類:
public class InterImpl implements Inter3{ // 該類實現了最下面的子介面,需要重寫所有的方法
@Override
public void method1() {
}
@Override
public void method2() {
}
@Override
public void method_abc() { // 重複的方法只需要實現一遍
}
@Override
public void method3() {
}
}
介面 3 繼承自介面 1 和介面 2,實現類實現了介面 3,則實現類中要重寫三個介面的全部方法。
練習:
編寫帶有介面和抽象類的標準 Javabean 類
我們現在有乒乓球運動員和籃球運動員,乒乓球教練和籃球教練。
為了出國交流,跟乒乓球相關的人員都需要學習英語。
請用所有知識分析,在這個案例中,哪些是具體類,哪些是抽象類,哪些是介面?
乒乓球運動員:姓名,年齡,學打乒乓球,說英語
籃球運動員:姓名,年齡,學打籃球
乒乓球教練:姓名,年齡,教打乒乓球,說英語
籃球教練:姓名,年齡,教打籃球
Javabean 類:
package demo5;
// 因為現在我不想讓外界去直接建立人的物件
// 因為直接建立頂層父類人的物件此時是沒有意義的
// 所以我就把他寫為抽象的。
public abstract class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package demo5;
public abstract class Sporter extends Person {
public Sporter() {
}
public Sporter(String name, int age) {
super(name, age);
}
public abstract void study();
}
package demo5;
public abstract class Coach extends Person {
public Coach() {
}
public Coach(String name, int age) {
super(name, age);
}
public abstract void teach();
}
package demo5;
public class PingPangSporter extends Sporter implements English {
public PingPangSporter() {
}
public PingPangSporter(String name, int age) {
super(name, age);
}
@Override
public void speakEnglish() {
System.out.println("乒乓球運動員說英語。");
}
@Override
public void study() {
System.out.println("乒乓球運動員學習怎麼打乒乓球。");
}
}
package demo5;
public class BasketballSporter extends Sporter {
public BasketballSporter() {
}
public BasketballSporter(String name, int age) {
super(name, age);
}
@Override
public void study() {
System.out.println("籃球運動員在學打籃球。");
}
}
package demo5;
public class PingPangCoach extends Coach implements English {
public PingPangCoach() {
}
public PingPangCoach(String name, int age) {
super(name, age);
}
@Override
public void teach() {
System.out.println("乒乓球教練在教別人打乒乓球。");
}
@Override
public void speakEnglish() {
System.out.println("乒乓球教練在學習英語。");
}
}
package demo5;
public class BasketballCoach extends Coach {
public BasketballCoach() {
}
public BasketballCoach(String name, int age) {
super(name, age);
}
@Override
public void teach() {
System.out.println("籃球教練在教別人打籃球。");
}
}
介面類:
package demo5;
public interface English {
public abstract void speakEnglish();
}
測試類:
package demo5;
public class Test {
public static void main(String[] args) {
PingPangSporter pps = new PingPangSporter("福原愛", 23);
System.out.println(pps.getName() + ", " + pps.getAge());
pps.study();
pps.speakEnglish();
}
}
執行結果:
福原愛, 23
乒乓球運動員學習怎麼打乒乓球。
乒乓球運動員說英語。
JDK 8 開始介面中新增的方法
JDK 7 之前,介面中只能定義抽象方法。
JDK 8 的新特性:介面中可以定義有方法體的方法,分兩種,一種是預設的方法,一種是靜態的方法。
JDK 9 的新特性:介面中可以定義私有方法,即用 private 修飾。
Java 為什麼這麼設計?
按照 JDK 7 的規定,如果介面變了,那麼所有的實現類都需要進行修改。
介面中有方法體的方法,是在介面升級時,為了相容性而使用的。介面新增了有方法體的方法後,實現類不需要立馬重寫,不重寫也不會報錯,當這個實現類要用到這個方法的時候再去重寫即可。
JDK 8 開始,允許在介面中定義預設方法,用關鍵字 default 修飾。作用就是解決介面升級帶來的相容性問題。
介面中預設方法的定義格式:
public default 返回值型別 方法名 (引數列表) {}
例如:
public default void show () {}
預設方法不是抽象方法,不會被強制重寫,但是如果被重寫,重寫的時候要去掉 default 關鍵字。
public 是預設修飾符,可以省略,但是 default 不能被省略。
如果一個實現類實現了多個介面,多個介面中存在相同名字的預設方法,子類就必須對這個方法進行重寫。
程式示例:
介面:
package demo6;
public interface Inter {
public abstract void method();
public default void show() {
System.out.println("介面的預設方法 ---- show");
}
}
實現:
package demo6;
public class ImplInter implements Inter{
@Override
public void method() {
System.out.println("實現類的 method 方法。");
}
}
測試類:
package demo6;
public class Test {
public static void main(String[] args) {
ImplInter ii = new ImplInter();
ii.method();
ii.show();
}
}
執行結果:
實現類的 method 方法。
介面的預設方法 ---- show
程式示例:
介面:
package demo6;
public interface Inter {
public abstract void method();
public default void show() {
System.out.println("介面的預設方法 ---- show");
}
}
實現類:
package demo6;
public class ImplInter implements Inter {
@Override
public void method() {
System.out.println("實現類的 method 方法。");
}
@Override
public void show() {
System.out.println("重寫介面的預設方法。");
}
}
測試類:
package demo6;
public class Test {
public static void main(String[] args) {
ImplInter ii = new ImplInter();
ii.method();
ii.show();
}
}
執行結果:
實現類的 method 方法。
重寫介面的預設方法。
如果兩個介面中有同名的預設方法, 則實現了這兩個介面的類必須重寫這個方法, 否則類的物件在呼叫這個預設方法時, 不知道是呼叫哪個介面中的預設方法. 程式示例:
第一個介面:
public interface InterA {
public abstract void methodA();
public default void show() {
System.out.println("InterA 裡面的 show()");
}
}
第二個介面:
public interface InterB {
public abstract void methodB();
public default void show() {
System.out.println("InterB 裡面的 show()");
}
}
實現類:
public class InterImpl implements InterA,InterB{
@Override
public void methodA() {
}
@Override
public void show() { // 這個 default 方法被強制重寫,因為兩個介面都有這個方法,
System.out.println("實現類中的重寫的預設方法");
}
@Override
public void methodB() {
}
}
測試類:
public class Test {
public static void main(String[] args) {
InterImpl inter = new InterImpl();
inter.show(); // 實現類中的重寫的預設方法
}
}
JDK 8 開始允許在介面中定義靜態方法,用 static 修飾。
介面中的靜態方法的定義格式:
public static 返回值型別 方法名 (引數列表) {}
例如:
public static void show () {}
介面中靜態方法的注意事項:
靜態方法只能透過介面名呼叫,不能透過實現類名或物件名來呼叫。
public 可以省略,但是 static 不能被省略。如果 static 省略不寫, 則會被當為抽象方法, 因為 abstract 是預設的, 不寫就預設是 abstract.
靜態方法不需要重寫.
程式示例:
介面程式碼:
public interface Inter {
// 介面中的抽象方法
public abstract void method();
// 介面中的靜態方法
public static void show() {
System.out.println("介面中的靜態方法");
}
}
實現類的程式碼:
public class InterImpl implements Inter{
// 重寫介面的抽象方法
@Override
public void method() {
System.out.println("InterImpl 重寫的抽象方法.");
}
// 介面的靜態方法不需要重寫
// 這個不是重寫, 只是實現類裡面有一個和介面裡面同名的靜態方法
// 如果呼叫時, 用 InterImpl.show(), 則呼叫的是子類裡面的 show() 方法,
// 用 Inter.show(), 則呼叫的是介面裡面的 show() 方法
public static void show(){
System.out.println("InterImpl 的 show() 方法.");
}
}
測試類:
public class Test {
public static void main(String[] args) {
// 呼叫介面中的show()方法
Inter.show(); // 介面中的靜態方法
// 呼叫實現類中的靜態方法
InterImpl.show(); // InterImpl 的 show() 方法.
}
}
私有方法分兩種:普通的和靜態的。
介面中私有方法的定義格式:
普通私有方法,是給預設方法服務的:
格式:private 返回值型別 方法名(引數列表){}
範例:private void show(){}
加了 static 的私有方法,即靜態的私有方法,是給靜態方法服務的:
格式:private static 返回值型別 方法名(引數列表){}
範例:private static void method(){}
程式示例:
介面:
package demo6;
public interface Inter {
public default void show1() {
System.out.println("show1方法開始執行。");
System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
}
public default void show2() {
System.out.println("show2方法開始執行。");
System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
}
}
顯然兩個方法中有重複程式碼,可以進行抽取。
package demo6;
public interface Inter {
public default void show1() {
System.out.println("show1方法開始執行。");
show3();
}
public default void show2() {
System.out.println("show2方法開始執行。");
show3();
}
public default void show3() {
System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
}
}
抽取出來之後在 show1() 和 show2() 中進行呼叫這個抽取出來的 show3() 即可。
但是這個方法不希望被外面的其他類呼叫,因為毫無意義。因此規定其為 private 並刪掉 default。
package demo6;
public interface Inter {
public default void show1() {
System.out.println("show1方法開始執行。");
show3();
}
public default void show2() {
System.out.println("show2方法開始執行。");
show3();
}
private void show3() { // 這就是普通的私有方法, 即沒有用 static 修飾的私有方法
System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
}
}
針對靜態方法則需要靜態私有方法為其服務:
package demo6;
public interface Inter {
public static void show1() {
System.out.println("show1方法開始執行。");
show4();
}
public static void show2() {
System.out.println("show2方法開始執行。");
show4();
}
private void show3() {
System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
}
private static void show4() { // 加 static 修飾的靜態私有方法
System.out.println("記錄程式在開始執行之後的各種細節,這裡有100行程式碼。");
}
}
當一個方法的形參是介面時, 可以傳遞介面所有實現類的物件, 這種方式稱為介面多型.