Java学习系列之抽象类和接口的区别和联系
導讀 本文首先分別介紹抽象類和接口的基礎概念、特征和應用場景,然后介紹其區別和聯系。
1 抽象類
1.1 定義抽象類
在Java中被abstract關鍵字修飾的類稱為抽象類,被abstract關鍵字修飾的方法稱為抽象方法,抽象方法只有方法的聲明,沒有方法體。
public abstract class AbstractPlayer {public abstract void eat(); }關于抽象類的命名,《阿里的 Java 開發手冊》上有強調,“抽象類命名要使用 Abstract 或 Base 開頭”。
1.2 抽象類的特征
1 抽象類是用來捕捉子類的通用特性的,它不能被實例化,只能被繼承。如果嘗試通過new關鍵字實例化的話,編譯器會報錯,提示類是抽象的不能被實例化:
抽象類的子類通過extends關鍵詞來繼承抽象類:
但是要注意:使用extends只能單繼承,同事繼承多個抽象類編譯器會報錯class cannot extend multiple classes。
2 包含抽象方法的一定是抽象類,但抽象類不一定含有抽象方法。
當我們嘗試在一個普通類中定義抽象方法的時候,編譯器會有兩處錯誤提示。第一處在類級別上,提示“這個類必須通過 abstract 關鍵字定義”,見下圖。
第二處在嘗試定義 abstract 的方法上,提示“抽象方法所在的類不是抽象的”,見下圖。
而在抽象類中,可以定義普通方法,如下面的代碼所示:
3 抽象類中的抽象方法只能為public或protected(如果是private則不能被子類繼承),默認為public。
4 抽象類中的抽象方法只有方法體,沒有具體實現,但可以有普通方法。
5 如果一個子類實現了父類(抽象類)的所有抽象方法,那么該子類可以不必是抽象類,否則就是抽象類。
public abstract class AbstractPlayer {public abstract void eat(); }/*** AbstractFootballPlayer沒有實現AbstractPlayer的抽象方法play,因此他也只能是抽象類*/ public abstract class AbstractFootballPlayer extends AbstractPlayer{public abstract void run(); }/*** FootballPlayer需要實現AbstractFootballPlayer中的所有抽象方法,否則它仍然是一個抽象類*/ public class FootballPlayer extends AbstractFootballPlayer{@Overridepublic void run() {}@Overridevoid play() {} }6 抽象類可以包含屬性、方法、構造方法等,但是構造方法不能用于實例化,主要用途是被子類調用。
public abstract class AbstractPlayer {// 可以定義構造函數AbstractPlayer(int count) {this.count = count;}// 可以定義靜態常量public static final int MAX_COUNT = 5;// 可以定義變量public int count;// 可以定義普通函數public void sayName(String name) {System.out.println("My name is " + name);}// 可以定義抽象方法abstract void play(); }// 如果抽象類定義了構造函數,其子類就需要調用抽象類的構造函數 public class BasketballPlayer extends AbstractPlayer{BasketballPlayer(int count) {super(count);}@Overridevoid play() {System.out.println(this.MAX_COUNT);System.out.println(this.count);}public static void main(String[] args) {AbstractPlayer player = new BasketballPlayer(11);player.sayName("Tom");player.play();} }1.3 抽象類的應用場景
場景1:希望一些通用的功能能夠被多個子類復用
比如說,AbstractPlayer 抽象類中有一個普通的方法 sleep(),表明所有運動員都需要休息,那么這個方法就可以被子類復用。
abstract class AbstractPlayer {public void sleep() {System.out.println("運動員也要休息而不是挑戰極限");} }// 子類 BasketballPlayer 繼承了 AbstractPlayer 類,就擁有了 sleep() 方法。 class BasketballPlayer extends AbstractPlayer { }// 子類 FootballPlayer 繼承了 AbstractPlayer 類,也就擁有了 sleep() 方法。 class FootballPlayer extends AbstractPlayer { }// BasketballPlayer 的對象可以直接調用父類的 sleep() 方法 BasketballPlayer basketballPlayer = new BasketballPlayer(); basketballPlayer.sleep();// FootballPlayer 的對象可以直接調用父類的 sleep() 方法 FootballPlayer footballPlayer = new FootballPlayer(); footballPlayer.sleep();場景2:希望所有子類能夠自行實現在抽象類中定義的抽象方法
比如說,AbstractPlayer 抽象類中定義了一個抽象方法 play(),表明所有運動員都可以從事某項運動,但需要對應子類去擴展實現,表明籃球運動員打籃球,足球運動員踢足球。
abstract class AbstractPlayer {abstract void play(); }public class BasketballPlayer extends AbstractPlayer {@Overridevoid play() {System.out.println("我是張伯倫,我籃球場上得過 100 分,");} }public class FootballPlayer extends AbstractPlayer {@Overridevoid play() {System.out.println("我是C羅,我能接住任意高度的頭球");} }2 接口
2.1 定義接口
Java中的接口使用interface關鍵字修飾,接口是方法的集合。
public interface Runnable {public abstract void run(); }2.2 接口的特征
public interface Electronic {// 常量String LED = "LED";// 抽象方法int getElectricityUse();// 靜態方法static boolean isEnergyEfficient(String electtronicType) {return electtronicType.equals(LED);}// 默認方法default void printDescription() {System.out.println("電子");} }1 接口中可以含有 變量和方法,但是要注意,接口中定義的變量會在編譯的時候自動加上 public static final 修飾符(并且只能是public static final變量,用private修飾會報編譯錯誤),而方法會被隱式地指定為public abstract方法且只能是public abstract方法(用其他關鍵字,比如private、protected、static、 final等修飾會報編譯錯誤)。
2 Java8以前接口中所有的方法不能有具體的實現,也就是說,接口中的方法必須都是抽象方法。
從這里可以隱約看出接口和抽象類的區別,接口是一種極度抽象的類型,它比抽象類更加“抽象”,并且一般情況下不在接口中定義變量。
3 從 Java 8 開始,接口中允許有靜態方法,比如上例中的 isEnergyEfficient() 方法。
靜態方法無法由(實現了該接口的)類的對象調用,它只能通過接口名來調用,比如說 Electronic.isEnergyEfficient(“LED”)。
4 從 Java8 開始,接口中允許定義default方法,比如上例中的 printDescription() 方法。
default方法提供了默認的實現,實現該接口的子類可以不修改default方法直接使用模式實現,也可以override。
5 接口不允許直接實例化,否則編譯器會報錯:
6 接口可以是空的,既可以不定義變量,也可以不定義方法。最典型的例子就是 Serializable 接口,Serializable 接口用來為序列化的具體實現提供一個標記,也就是說,只要某個類實現了 Serializable 接口,那么它就可以用來序列化了。
7 接口支持多繼承,即一個接口可以extends多個接口,間接的解決了Java中類的單繼承問題。
2.3 接口的應用場景
場景1 作為標記,使某些實現類具有我們想要的功能
比如說,實現了 Cloneable 接口的類具有拷貝的功能,實現了 Comparable 或者 Comparator 的類具有比較功能。Cloneable 和 Serializable 一樣,都屬于標記型接口,它們內部都是空的。實現了 Cloneable 接口的類可以使用 Object.clone() 方法,否則會拋出 CloneNotSupportedException。
public class CloneableTest implements Cloneable {@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}public static void main(String[] args) throws CloneNotSupportedException {CloneableTest c1 = new CloneableTest();CloneableTest c2 = (CloneableTest) c1.clone();} }運行后沒有報錯。現在把 implements Cloneable 去掉。
public class CloneableTest {@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}public static void main(String[] args) throws CloneNotSupportedException {CloneableTest c1 = new CloneableTest();CloneableTest c2 = (CloneableTest) c1.clone();} }運行后拋出 CloneNotSupportedException:
Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTestat java.base/java.lang.Object.clone(Native Method)at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)場景2 借助接口實現多重繼承
如果有兩個類共同繼承(extends)一個父類,那么父類的方法就會被兩個子類重寫。然后,如果有一個新類同時繼承了這兩個子類,那么在調用重寫方法的時候,編譯器就不能識別要調用哪個類的方法了。這也正是著名的菱形問題,見下圖。
而借助接口可以達到多重繼承的目的。
場景3 實現多態
使用抽象類和接口都可以實現多態,下面舉個栗子:
public interface Shape {String name(); }// Circle 類實現了 Shape 接口,并重寫了 name() 方法 public class Circle implements Shape {@Overridepublic String name() {return "圓";} }// Square 類也實現了 Shape 接口,并重寫了 name() 方法 public class Square implements Shape {@Overridepublic String name() {return "正方形";} }// 調用看看效果 List<Shape> shapes = new ArrayList<>(); Shape circleShape = new Circle(); Shape squareShape = new Square();shapes.add(circleShape); shapes.add(squareShape);for (Shape shape : shapes) {System.out.println(shape.name()); }// 輸出結果為: // 圓 // 正方形這就實現了多態,變量 circleShape、squareShape 的引用類型都是 Shape,但執行 shape.name() 方法的時候,Java 虛擬機知道該去調用 Circle 的 name() 方法還是 Square 的 name() 方法。
場景4 接口在設計模式中的應用
在使用接口的時候,經常會用到三種模式,分別是策略模式、適配器模式和工廠模式。
策略模式
策略模式的思想是,針對一組算法,將每一種算法封裝到具有共同接口的實現類中,接口的設計者可以在不影響調用者的情況下對算法做出改變。示例如下:
// 接口:教練 interface Coach {// 方法:防守void defend(); }// 何塞·穆里尼奧 class Hesai implements Coach {@Overridepublic void defend() {System.out.println("防守贏得冠軍");} }// 德普·瓜迪奧拉 class Guatu implements Coach {@Overridepublic void defend() {System.out.println("進攻就是最好的防守");} }public class Demo {// 參數為接口public static void defend(Coach coach) {coach.defend();}public static void main(String[] args) {// 為同一個方法傳遞不同的對象defend(new Hesai());defend(new Guatu());} }適配器模式
適配器模式的思想是,針對調用者的需求對原有的接口進行轉接。生活當中最常見的適配器就是HDMI(英語:High Definition Multimedia Interface,中文:高清多媒體接口)線,可以同時發送音頻和視頻信號。適配器模式的示例如下:
interface Coach {void defend();void attack(); }// 抽象類實現接口,并置空方法 abstract class AdapterCoach implements Coach {public void defend() {};public void attack() {}; }// 新類繼承適配器 class Hesai extends AdapterCoach {public void defend() {System.out.println("防守贏得冠軍");} }public class Demo {public static void main(String[] args) {Coach coach = new Hesai();coach.defend();} }工廠模式
所謂的工廠模式理解起來也不難,就是什么工廠生產什么,比如說寶馬工廠生產寶馬,奔馳工廠生產奔馳,A 級學院畢業 A 級教練,C 級學院畢業 C 級教練。示例如下:
// 教練 interface Coach {void command(); }// 教練學院 interface CoachFactory {Coach createCoach(); }// A級教練 class ACoach implements Coach {@Overridepublic void command() {System.out.println("我是A級證書教練");}}// A級教練學院 class ACoachFactory implements CoachFactory {@Overridepublic Coach createCoach() {return new ACoach();}}// C級教練 class CCoach implements Coach {@Overridepublic void command() {System.out.println("我是C級證書教練");}}// C級教練學院 class CCoachFactory implements CoachFactory {@Overridepublic Coach createCoach() {return new CCoach();}}public class Demo {public static void create(CoachFactory factory) {factory.createCoach().command();}public static void main(String[] args) {// 對于一支球隊來說,需要什么樣的教練就去找什么樣的學院// 學院會介紹球隊對應水平的教練。create(new ACoachFactory());create(new CCoachFactory());} }3 抽象類和接口的共同點和區別
3.1 共同點
- 都不能被實例化
- 都可以包含抽象方法
- 都可以有默認實現的方法(Java 8 可以用 default 關鍵在接口中定義默認方法)
3.2 區別
- 接口主要用于對類的行為進行約束,你實現了某個接口就具有了對應的行為。抽象類主要用于代碼復用,強調的是所屬關系(比如說我們抽象了一個發送短信的抽象類)。
- 一個類只能繼承一個類,但是可以實現多個接口(如果想實現多繼承就用接口)。
- 接口中的成員變量只能是 public static final 類型的,不能被修改且必須有初始值,而抽象類的成員變量默認 default,可在子類中被重新定義,也可被重新賦值。
4 抽象類和接口的應用場景
1、如果你擁有一些方法并且想讓它們中的一些有默認實現,那么使用抽象類吧。
2、如果你想實現多重繼承,那么你必須使用接口。由于Java不支持多繼承,子類不能夠繼承多個類,但可以實現多個接口。因此你就可以使用接口來解決它。
3、如果基本功能在不斷改變,那么就需要使用抽象類。如果不斷改變基本功能并且使用接口,那么就需要改變所有實現了該接口的類。
本文參考博客
- JavaGuide-接口和抽象類有什么共同點和區別?
- Java抽象類,看這一篇就夠了,豁然開朗
- Java接口,看這一篇就夠了,簡單易懂
- Java 抽象類和接口的區別,看這一篇就夠了
總結
以上是生活随笔為你收集整理的Java学习系列之抽象类和接口的区别和联系的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谷歌搜索留痕组合工具,批量生成
- 下一篇: 模板类的声明和定义