1. 程式人生 > >一文打盡Java抽象類和介面的相關問題

一文打盡Java抽象類和介面的相關問題

相關文章: 1. [《面向物件再探究》](https://mp.weixin.qq.com/s/NQK3aYXUh3eOwmwmP_VluQ):介紹了面向物件的基本概念 2. [《詳解Java的物件建立》](https://mp.weixin.qq.com/s/lCsGhvovwWEw4DE4r92a3Q):介紹了物件的建立、構造器的使用 3. [《一文打盡Java繼承的相關問題》](https://mp.weixin.qq.com/s/mo6EQ3Clji2L7SyOVMea5w):介紹了繼承的使用方式和注意事項 本文來介紹Java的抽象類和介面的使用。 # 1. 抽象類 在前面的文章中提到過:父類比子類更加抽象,子類比父類更加具體。 在[《一文打盡Java繼承的相關問題》](https://mp.weixin.qq.com/s/mo6EQ3Clji2L7SyOVMea5w)這篇文章中舉了動物和狗的例子: ```java public class Animal { private String name; private int age; public Animal(String name, int age) { this.name = name; this.age = age; } public Animal() { } public void say() { System.out.println("我是" + name + ",今年" + age + "歲了"); } //getters and setters ... } ``` ```java public class Dog extends Animal { private String address; public Dog(String name, int age, String address) { super(name, age); this.address = address; } public Dog() { } public void say() { System.out.println("我叫" + super.getName() + ",今年" + super.getAge() + "歲了,家住" + address + ",汪汪汪..."); } public void watchDoor() { System.out.println("我在" + address +"看門..."); } //getters and setters ... } ``` 上面`Dog`類很自然地繼承了`Animal`類,沒毛病!但是仔細琢磨一下`Animal`類的`say()`方法。 動物是一個很寬泛的概念,如果寫程式碼表示,我們平常見到的大部分生物都可以繼承該類。如下圖: ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9naXRlZS5jb20veGluZ3Jlbmd1YW54dWUvYmxvZy1pbWFnZXMvcmF3L21hc3Rlci9pbWdzLzIwMjAwODA0MTA0ODQwLnBuZw?x-oss-process=image/format,png) 有這麼多類繼承`Animal`類,那`Animal`類的`say()`方法這樣寫合適嗎?因為每種動物說話的方式都不同,而動物又是這麼寬泛的一個概念,我們通常會`new Dog()`或`new People()`,但很少會去`new Animal()`。 所以`Animal`類的`say()`方法體對其來說就是雞肋一般的存在,因為即使有方法體,也會被其子類重寫。既然這樣那就乾脆不要方法體了。 換句話說,我們將`Animal`類再進行更高層次地抽象,它具有各種動物都有的屬性,比如`name`、`age`,也有各種動物都有的行為,比如`say`。**但是,`Animal`類並不具體實現該方法,具體的實現交給子類來做**。 這樣一來,誰繼承了`Animal`類,誰就有了它的屬性和行為,子類不用管父類的行為是否合適,因為父類的行為“有名無實”,所以子類只需繼承這些“名”,具體的“實”則由子類來完成。 這樣的`Animal`類就是**抽象類**。 下面將上例中的`Animal`類修改為抽象類,`Dog`類無需改動: ```java public abstract class Animal { private String name; private int age; public Animal(String name, int age) {//有參構造器 this.name = name; this.age = age; } public Animal() {//無參構造器 } public abstract void say();//抽象方法 public String getName() {//被具體實現的方法 return name; } public void setName(String name) { this.name = name; } //getters and setters.. } ``` 下面是抽象類的特點: (一)抽象類被`abstract`關鍵字修飾。 ```java public abstract class Animal { //...... } ``` (二)類中沒有方法體的方法叫**抽象方法**,也需要使用`abstract`關鍵字修飾。 ```java public abstract void say();//抽象方法 ``` (三)有抽象方法的類一定是抽象類。 (四)沒有抽象方法的類也可以是抽象類。 (五)抽象類中可以有成員變數、構造器、被具體實現的方法。構造器不能是抽象的。 (六)抽象類不能被例項化,但是可以宣告一個抽象類變數 ```java Animal animal = new Animal();//報錯: 'Animal' is abstract; cannot be instantiated Anima animal;//抽象類變數,可行 ``` 我們以前遇到的類,比如`Dog`類,是用來描述物件的,但抽象類的方法沒有具體實現,所以它沒有足夠的資訊來描述物件,所以抽象類只能被繼承用來描述其子類而不能例項化。 (七)子類擴充套件抽象父類有兩種選擇: 1. 當子類不是抽象類時,子類必須實現抽象父類的抽象方法: ```java public class Dog extends Animal { //屬性、構造器、其他方法 //實現抽象父類的抽象方法 @Override public void say() { System.out.println("我叫" + super.getName() + ",今年" + super.getAge() + "歲了,家住" + address + ",汪汪汪..."); } } ``` 2. 當子類是抽象類時,子類可以實現抽象父類的抽象方法,也可以選擇不實現。 ```java public abstract class Dog extends Animal { //屬性、構造器、其他方法 //可以選擇不實現父類的抽象方法 } ``` # 2. 介面 ## 2.1. 什麼是介面? 生活中有一種介面是大家每天都在用的,那就是插座。 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9naXRlZS5jb20veGluZ3Jlbmd1YW54dWUvYmxvZy1pbWFnZXMvcmF3L21hc3Rlci9pbWdzLzIwMjAwODA0MTcyNzA4LnBuZw?x-oss-process=image/format,png#pic_center) 不管是什麼樣的電器,冰箱、電視、電腦、電風扇,只要買回來,就能插上通電。之所以這麼便利,就是因為電器生產商和插座生產商都遵守了一個**生產規範**:我把插座生產成這個模樣,你把插頭也生產成這個模樣,我不管你電器內部是啥樣,你也不用管我的插座內部是啥樣,大家把產品生產好,各自賣給客戶,合作愉快,一起賺錢。這些產品如果以後壞了,那就再買一個遵守規範的就能繼續配套使用了。 想一想,如果沒有這個規範,插座和插頭生產的千奇百怪,買回來怎麼用?只能把插座和冰箱拆開,用手接220V的電線了。 換句話說,**介面就是生產規範 / 標準,或者雙方遵守的協議**,只要雙方都遵守,那麼就能愉快地交流、愉快地合作。 在Java開發中,一個軟體系統肯定不是由一個人完成的,而是由一個團隊完成的。那如何避免甲寫的類乙不能用,乙寫的類丙不能用呢,最後導致甲改了乙的程式碼,乙改了丙的程式碼?**事先定好規範(介面)**,大家都遵守介面,只要我知道你的介面,那麼我不需要知道你的具體程式碼,就能呼叫你的類。 ## 2.2. 介面的使用 掌握關於介面幾個特點就能愉快地寫介面了: (一)Java中使用`inteface`關鍵字來宣告一個介面,介面必須是`public`的,只有公有了才能讓大家遵守: ```java public interface Runnable { } ``` (二)介面中通常寫各種抽象方法,但只宣告,不能寫方法體。 ```java public interface Runnable { /*public abstract*/ void run(); } ``` (三)不必將方法宣告為`public abstract` ,在介面中的所有方法都預設的是`public abstract`修飾。 (四)介面中不能有成員變數、靜態程式碼塊。 (五)介面中可以含有常量,常量預設是`public static final`修飾。 ```java public interface Runnable { /*public static final*/ int i = 1; void run(); } ``` (六)類通過`implements`關鍵字**實現介面**,必須同時實現介面中的所有方法。 ```java public class Dog implements Runnable { @Override public void run() { System.out.println("跑得飛快"); } //...... } ``` (七)如果實現介面的類是抽象類,可以不用實現介面中的方法。 (八)一個類可以同時繼承類和實現介面。 ```java public class Dog extends Animal implements Runnable { //...... } ``` (九)一個類可以實現多個介面。 ```java public class Dog implements Runnable, Flyable { //...... } ``` (十)介面之間可以繼承,並且允許多繼承。 ```java public interface A { //...... } public interface B extends A, Runnable{ //...... } ``` (十一)介面不能被例項化,但是可以宣告一個介面變數。 ```java Runnable runnable = new Runnable();//'Runnable' is abstract; cannot be instantiated Runnable runnable;//介面變數,可行 ``` # 3. 總結 抽象類用`abstract`關鍵字宣告,抽象類中除了可以有抽象方法(用`abstract`關鍵字宣告)外,還可以有普通類的成員變數、構造器、方法。不能被例項化,但可以宣告抽象類的變數。子類繼承抽象類要實現父類的抽象方法(如果子類是抽象的,則不用實現)。 介面用`interface`關鍵字宣告,介面中只能有抽象方法、常量(忽略修飾符)。不能被例項化,但可以宣告介面變數。介面之間可以繼承,且允許多繼承。類實現介面使用`implements`關鍵字,且必須實現介面中的抽象方法(如果類是抽象的,則不用實現)。 總結了抽象類和介面的特點,發現抽象類好像也能作為“介面”使用。那有了抽象類,為什麼還要有介面? 類只能單繼承,如果使用抽象類作為“介面”,這意味著一個類只能遵守一份“介面”,顯然不符合實際。而介面則靈活多了。 # 4. [關於我](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow) > 如有錯誤,還請