Java繼承 vs 接口:何時(shí)用extends,何時(shí)用implements?

extends用于實(shí)現(xiàn)類間“is-a”關(guān)系,強(qiáng)調(diào)代碼復(fù)用與層次結(jié)構(gòu)表達(dá),適用于存在明確繼承關(guān)系且需共享實(shí)現(xiàn)的場(chǎng)景;implements用于實(shí)現(xiàn)接口定義的“can-do”契約,強(qiáng)調(diào)多態(tài)與解耦,適用于不同類共享行為規(guī)范的場(chǎng)景。1.extends核心優(yōu)勢(shì)在于提供代碼復(fù)用機(jī)制和清晰層次結(jié)構(gòu),適合強(qiáng)烈的“is-a”關(guān)系、代碼復(fù)用、擴(kuò)展現(xiàn)有功能及抽象基類設(shè)計(jì);2.implements通過(guò)接口實(shí)現(xiàn)多態(tài)性和解耦,使客戶端代碼僅依賴接口而非具體類,提高系統(tǒng)靈活性和可擴(kuò)展性;3.Java 8/9引入默認(rèn)方法、靜態(tài)方法和私有方法增強(qiáng)了接口功能,但未改變extends與implements的核心語(yǔ)義,選擇時(shí)仍需根據(jù)設(shè)計(jì)意圖判斷是繼承身份還是實(shí)現(xiàn)行為契約。

Java繼承 vs 接口:何時(shí)用extends,何時(shí)用implements?

Java中,extends 用于實(shí)現(xiàn)類之間的繼承關(guān)系,表達(dá)“is-a”的語(yǔ)義,即子類父類的一種特殊類型,它繼承了父類的所有非私有成員。而 implements 用于實(shí)現(xiàn)接口,表達(dá)“can-do”或“has-a-capability”的語(yǔ)義,即一個(gè)類承諾實(shí)現(xiàn)某個(gè)接口定義的所有行為,但不關(guān)心它“是什么”。選擇哪個(gè)取決于你想要表達(dá)的設(shè)計(jì)意圖和系統(tǒng)結(jié)構(gòu)。

Java繼承 vs 接口:何時(shí)用extends,何時(shí)用implements?

解決方案

說(shuō)實(shí)話,這其實(shí)是個(gè)老生常談的問(wèn)題,但每次聊起來(lái)都還是挺有意思的,因?yàn)樗|及了面向對(duì)象設(shè)計(jì)最核心的部分。我個(gè)人覺(jué)得,理解 extends 和 implements 的關(guān)鍵在于它們各自代表的“契約”和“關(guān)系”。

Java繼承 vs 接口:何時(shí)用extends,何時(shí)用implements?

當(dāng)你使用 extends 時(shí),你是在說(shuō):“我這個(gè)新類,它就是你那個(gè)老類的一個(gè)更具體的版本。” 比如,你有一個(gè) Animal 類,然后你創(chuàng)建了一個(gè) Dog 類,Dog extends Animal,這很自然。狗是一種動(dòng)物,它繼承了動(dòng)物的特性(比如有生命、會(huì)呼吸),然后在此基礎(chǔ)上增加了自己的特性(比如會(huì)吠叫)。這里面包含著代碼的復(fù)用,子類可以直接使用父類的方法和屬性,甚至可以重寫(xiě)(override)父類的方法來(lái)表現(xiàn)自己的獨(dú)特行為。但要注意,Java是單繼承的,一個(gè)類只能 extends 一個(gè)父類,這意味著你的“血統(tǒng)”是唯一的。這種強(qiáng)綁定帶來(lái)了代碼復(fù)用上的便利,但有時(shí)候也會(huì)導(dǎo)致耦合度較高,尤其是在類層次結(jié)構(gòu)設(shè)計(jì)不當(dāng)?shù)臅r(shí)候。

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

而 implements 則完全是另一種哲學(xué)。它代表的是一種“能力”或者“行為契約”。當(dāng)你 implements 一個(gè)接口時(shí),你是在承諾:“我這個(gè)類,不管我是誰(shuí),我都能做到這個(gè)接口里定義的所有事情。” 比如,你有一個(gè) Flyable 接口,里面定義了一個(gè) fly() 方法。現(xiàn)在 Bird 可以 implements Flyable,airplane 也可以 implements Flyable。鳥(niǎo)和飛機(jī)在本質(zhì)上是完全不同的東西,它們沒(méi)有繼承關(guān)系,但它們都具備“飛行”的能力。接口強(qiáng)制你實(shí)現(xiàn)它定義的所有抽象方法,這確保了多態(tài)性,因?yàn)槟憧梢酝ㄟ^(guò)接口類型來(lái)操作任何實(shí)現(xiàn)了該接口的對(duì)象,而無(wú)需關(guān)心它們的具體實(shí)現(xiàn)細(xì)節(jié)。這大大降低了類之間的耦合,提高了系統(tǒng)的靈活性和可擴(kuò)展性。你甚至可以實(shí)現(xiàn)多個(gè)接口,一個(gè)類既能飛又能跑(implements Flyable, Runnable),這在某種程度上彌補(bǔ)了Java單繼承的限制。

Java繼承 vs 接口:何時(shí)用extends,何時(shí)用implements?

總結(jié)一下,如果你的類之間存在明確的“is-a”層次結(jié)構(gòu),并且你想復(fù)用父類的實(shí)現(xiàn),那就用 extends。如果你的類之間沒(méi)有直接的繼承關(guān)系,但它們需要共享某種行為規(guī)范或能力,或者你想實(shí)現(xiàn)多態(tài)和解耦,那么 implements 就是你的首選。

extends 的核心優(yōu)勢(shì)與適用場(chǎng)景是什么?

extends 的核心優(yōu)勢(shì)在于它提供了一種強(qiáng)大的代碼復(fù)用機(jī)制和清晰的層次結(jié)構(gòu)表達(dá)。當(dāng)一個(gè)類 extends 另一個(gè)類時(shí),子類自動(dòng)獲得了父類的所有非私有成員(字段和方法)。這意味著你不需要在子類中重新編寫(xiě)父類已經(jīng)實(shí)現(xiàn)的功能,可以直接繼承并使用。這在構(gòu)建復(fù)雜的軟件系統(tǒng)時(shí)非常有用,比如你有一個(gè)基礎(chǔ)的 User 類,包含了用戶名、密碼等通用信息,然后你可以派生出 AdminUser 和 GuestUser,它們都繼承了 User 的基本功能,再各自添加特有的權(quán)限或行為。

適用場(chǎng)景通常是:

  • 強(qiáng)烈的“is-a”關(guān)系: 當(dāng)你能夠明確地說(shuō)“A是一種B”時(shí),比如“轎車是一種汽車”,“圓形是一種形狀”。這種關(guān)系通常是穩(wěn)定的,不會(huì)輕易改變。
  • 代碼復(fù)用: 當(dāng)多個(gè)子類需要共享大部分相同的實(shí)現(xiàn)邏輯時(shí),將其放在一個(gè)父類中,子類繼承即可避免代碼重復(fù)。
  • 擴(kuò)展現(xiàn)有功能: 你想在不修改原有類代碼的基礎(chǔ)上,增加新的功能或修改部分行為(通過(guò)方法重寫(xiě))。這符合開(kāi)放-封閉原則(OCP),即對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉。
  • 抽象基類: 當(dāng)你需要定義一個(gè)通用的模板,其中包含一些具體實(shí)現(xiàn)和一些需要子類去完成的抽象方法時(shí),可以使用抽象類(abstract class)并讓其他類去 extends 它。這提供了一種半成品的設(shè)計(jì),既有骨架也有肉。

我發(fā)現(xiàn)很多初學(xué)者,甚至是一些有經(jīng)驗(yàn)的開(kāi)發(fā)者,有時(shí)候會(huì)濫用繼承,導(dǎo)致類層次結(jié)構(gòu)過(guò)于深,或者出現(xiàn)“繼承爆炸”的問(wèn)題,即為了復(fù)用一點(diǎn)點(diǎn)代碼就引入了繼承,最終使得系統(tǒng)變得僵硬且難以維護(hù)。所以,在使用 extends 之前,最好問(wèn)問(wèn)自己:這真的是一個(gè)“is-a”關(guān)系嗎?

// 示例:extends 的應(yīng)用 class Vehicle {     protected String brand;      public Vehicle(String brand) {         this.brand = brand;     }      public void start() {         System.out.println(brand + " vehicle started.");     }      public void stop() {         System.out.println(brand + " vehicle stopped.");     } }  class Car extends Vehicle {     private int numberOfDoors;      public Car(String brand, int numberOfDoors) {         super(brand); // 調(diào)用父類構(gòu)造器         this.numberOfDoors = numberOfDoors;     }      // 重寫(xiě)父類方法     @Override     public void start() {         System.out.println(brand + " car engine purrs and starts.");     }      public void drive() {         System.out.println(brand + " car is driving with " + numberOfDoors + " doors.");     } }  // 使用 // Car myCar = new Car("Toyota", 4); // myCar.start(); // 輸出:Toyota car engine purrs and starts. // myCar.drive(); // 輸出:Toyota car is driving with 4 doors.

implements 如何實(shí)現(xiàn)多態(tài)與解耦?

implements 在Java中是實(shí)現(xiàn)多態(tài)和解耦的基石,它提供了一種非常靈活的機(jī)制來(lái)定義行為契約。當(dāng)一個(gè)類 implements 一個(gè)接口時(shí),它必須提供接口中所有抽象方法的具體實(shí)現(xiàn)。這就像簽了一份合同,你承諾會(huì)完成合同里列出的所有工作。

  • 多態(tài)性: 這是 implements 最強(qiáng)大的特性之一。通過(guò)接口,你可以編寫(xiě)出能夠處理多種不同類型對(duì)象的通用代碼。只要這些對(duì)象都實(shí)現(xiàn)了同一個(gè)接口,你就可以把它們當(dāng)作該接口的類型來(lái)處理。例如,你有一個(gè) Printable 接口,Document 類和 Image 類都實(shí)現(xiàn)了它。那么你可以有一個(gè) printAll(List items) 方法,它能打印任何實(shí)現(xiàn)了 Printable 接口的對(duì)象,而無(wú)需知道這些對(duì)象是文檔還是圖片。這種“向上轉(zhuǎn)型”的能力,讓代碼變得非常靈活和可擴(kuò)展。

  • 解耦: 接口將“做什么”與“如何做”分離開(kāi)來(lái)。客戶端代碼只需要知道它需要一個(gè)實(shí)現(xiàn)特定接口的對(duì)象,而不需要知道這個(gè)對(duì)象的具體類是什么。這意味著你可以輕松地替換掉某個(gè)接口的實(shí)現(xiàn),而不需要修改依賴于該接口的客戶端代碼。這對(duì)于單元測(cè)試、模塊化開(kāi)發(fā)以及大型系統(tǒng)的維護(hù)都至關(guān)重要。比如,你有一個(gè) Logger 接口,它有 logInfo()、logError() 等方法。你可以有 FileLogger、DatabaseLogger、ConsoleLogger 等多種實(shí)現(xiàn)。你的應(yīng)用程序代碼只需要依賴 Logger 接口,具體使用哪個(gè)實(shí)現(xiàn)可以在運(yùn)行時(shí)配置,或者通過(guò)依賴注入框架來(lái)管理。這種設(shè)計(jì)模式讓系統(tǒng)組件之間的依賴關(guān)系變得松散,降低了修改一個(gè)組件對(duì)其他組件的影響。

所以,當(dāng)你在設(shè)計(jì)系統(tǒng)時(shí),如果某個(gè)功能可以有多種不同的實(shí)現(xiàn)方式,或者你需要定義一組行為規(guī)范,而不想限制具體實(shí)現(xiàn)類的繼承關(guān)系,那么接口就是最佳選擇。它鼓勵(lì)“面向接口編程”,這是構(gòu)建健壯、可維護(hù)和可擴(kuò)展系統(tǒng)的關(guān)鍵。

// 示例:implements 的應(yīng)用 interface Drivable {     void accelerate();     void brake(); }  class SportsCar implements Drivable {     @Override     public void accelerate() {         System.out.println("SportsCar: Vroom! Accelerating fast!");     }      @Override     public void brake() {         System.out.println("SportsCar: Screech! Braking hard!");     } }  class Truck implements Drivable {     @Override     public void accelerate() {         System.out.println("Truck: Slowly gaining speed.");     }      @Override     public void brake() {         System.out.println("Truck: Grinding to a halt.");     } }  // 使用多態(tài) // public void testDrive(Drivable vehicle) { //     vehicle.accelerate(); //     vehicle.brake(); // } // // Drivable mySportsCar = new SportsCar(); // Drivable myTruck = new Truck(); // // testDrive(mySportsCar); // 調(diào)用 SportsCar 的方法 // testDrive(myTruck);     // 調(diào)用 Truck 的方法

Java 8/9 之后,接口的新特性如何影響 extends 與 implements 的選擇?

Java 8 和 Java 9 為接口引入了一些非常重要的特性,這在一定程度上模糊了接口和抽象類之間的界限,但并沒(méi)有從根本上改變 extends 和 implements 的核心設(shè)計(jì)哲學(xué)。

Java 8 引入的特性:

  • 默認(rèn)方法(default Methods): 接口現(xiàn)在可以包含帶有具體實(shí)現(xiàn)的 default 方法。這意味著你可以在不破壞現(xiàn)有實(shí)現(xiàn)類的情況下,為接口添加新方法。如果一個(gè)類實(shí)現(xiàn)了包含默認(rèn)方法的接口,它可以選擇不重寫(xiě)該默認(rèn)方法,直接使用接口提供的默認(rèn)實(shí)現(xiàn),也可以重寫(xiě)它。
  • 靜態(tài)方法(Static Methods): 接口也可以包含靜態(tài)方法。這些方法屬于接口本身,而不是任何實(shí)現(xiàn)該接口的對(duì)象。它們通常用于提供與接口相關(guān)的工具方法。

Java 9 引入的特性:

  • 私有方法(Private Methods): 接口可以包含私有方法,包括靜態(tài)私有方法。這些私有方法主要用于在接口內(nèi)部,為默認(rèn)方法或靜態(tài)方法提供輔助功能,避免代碼重復(fù)。

這些新特性讓接口變得更加強(qiáng)大和靈活。以前,如果想在接口中提供一些公共的、可復(fù)用的邏輯,你可能不得不創(chuàng)建一個(gè)抽象類。但現(xiàn)在,通過(guò)默認(rèn)方法,接口也能提供一部分實(shí)現(xiàn)。這在某些場(chǎng)景下,確實(shí)減少了對(duì)抽象類的依賴,因?yàn)榻涌谝材芴峁┠撤N程度的代碼復(fù)用。

對(duì)選擇的影響:

  • 接口的“能力”增強(qiáng): 默認(rèn)方法讓接口在定義行為契約的同時(shí),也能提供一些通用的、默認(rèn)的行為實(shí)現(xiàn)。這對(duì)于庫(kù)和框架的維護(hù)者來(lái)說(shuō)非常方便,他們可以在不破壞兼容性的前提下擴(kuò)展接口。
  • 抽象類與接口的區(qū)分依然存在: 盡管接口有了默認(rèn)方法,但它仍然不能有實(shí)例字段(非 static final 字段),也不能有構(gòu)造器。抽象類則可以擁有這些。所以,如果你的基類需要維護(hù)狀態(tài)(實(shí)例字段)或需要復(fù)雜的初始化邏輯(構(gòu)造器),那么抽象類(通過(guò) extends)仍然是更合適的選擇。
  • 核心語(yǔ)義不變: extends 依然強(qiáng)調(diào)“is-a”的層次結(jié)構(gòu)和實(shí)現(xiàn)繼承,而 implements 依然強(qiáng)調(diào)“can-do”的行為契約和多態(tài)性。默認(rèn)方法只是為接口提供了一種“可選的”行為實(shí)現(xiàn),而不是強(qiáng)制的“身份”繼承。

所以,我的看法是,這些新特性并沒(méi)有顛覆 extends 和 implements 的基本選擇原則。它們更多地是作為一種工具,讓你在設(shè)計(jì)時(shí)有更多的選擇,尤其是在處理接口演進(jìn)和提供通用實(shí)用方法時(shí)。但在決定是建立“is-a”關(guān)系還是“can-do”能力時(shí),你依然需要回到最初的那個(gè)問(wèn)題:你的設(shè)計(jì)意圖是什么?是繼承身份和狀態(tài),還是實(shí)現(xiàn)行為契約?這才是關(guān)鍵。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊7 分享