Java的抽象類與介面理解

fghbv發表於2020-12-03

抽象類


概述

在物件導向的概念中,所有的物件都是通過類來描繪的,但是反過來,並不是所有的類都是用來描繪物件的,如果一個類中沒有包含足夠的資訊來描繪一個具體的物件,這樣的類就是抽象類。

抽象類往往用來表徵對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。比如我們在建立動物物件的時候,我們不知道是我們要建立的到底是什麼動物我們就可以用抽象類來定義。

抽象類,其實就是把子類中的共有的特性給提取出來,用於繼承實現複用。

特點:

  • 抽象類不能被例項化,例項化的工作應該交由它的子類來完成,它只需要有一個引用即可。
  • 抽象類不一定有抽象方法,有抽象方法的類一定為抽象類。
  • 如果一個類繼承於一個抽象類,則子類必須實現父類的抽象方法。如果子類沒有實現父類的抽象方法,則必須將子類也定義為為abstract類
  • 有成員變數,成員變數可以是變數,也可以是常量。
  • 有構造方法,用於子類訪問父類資料的初始化
  • 有成員方法,成員方法可以是抽象的,也可以是非抽象的。
    抽象方法:限定子類必須完成某些動作
    非抽象方法:提高程式碼的複用性

程式碼

//抽象動物類
public abstract class Animal {
	//抽象方法
	public abstract void eat();
	
	public void sleep() {
		System.out.println("睡覺");
	}
}

//貓類
public class Cat extends Animal{
	//重寫父類中的方法
	@Override
	public void eat() {
		// TODO 自動生成的方法存根
		System.out.println("貓吃魚");
	}
	
}

//狗類
public abstract class Dog extends Animal{
	//子類沒有重寫父類(抽象類)中的所有方法,所以它需要為抽象類
}


//測試類
public class AnimalDemo {
	public static void main(String[] args) {
		//建立物件
		/*Animal a = new Animal();//抽象類不能例項化
*/		
		//按照多型的形式例項化抽象類
		Animal a = new Cat();
		a.eat();
		a.sleep();
	}
}

注意

  1. abstract不能與final並列修飾同一個類
  2. abstract 不能與private、static、final或native並列修飾同一個方法
  3. 子類中的抽象方法不能與父類的抽象方法同名

介面


概述

Java中的介面是一系列方法的宣告,是一些方法特徵的集合,一個介面只有方法的特徵沒有方法的實現,它就是一種規範。怎麼理解呢,就是我提供這樣一個標準,你通過接入(實現)我這個介面,你能實現我提供的標準,拿到“許可證”,具體你拿到了這個證做什麼那看你了。例如,假如你有一個店面,現在你要想經營一個餐飲店,那你是不是至少需要一個餐飲經營許可證(別的人要也想開餐飲店,那他是不是也是需要接入這個介面),這個餐飲經營許可證就像一個介面,你接入(實現)了這個介面,這表示你可以經營了,但是你具體是賣早餐還是賣午餐則需要你自己去選擇。

特點

介面的特點
      A:定義介面使用的是interface關鍵字
      B:類和介面之間是實現關係,用implements關鍵字表示
      C:介面不能例項化
            參照多型的形式使用實現類來例項化。(重寫方法)
      D:介面的實現類
            要麼重寫介面中的所有的抽象方法
            要麼是一個抽象類

多型的幾種形式
      具體類多型(幾乎不用)
      抽象類多型(常用)
      介面多型(最常用)

介面的成員特點

  • 成員變數:
    有成員變數,而且變數只能是常量。
    預設修飾符:public static final
  • 構造方法:
    沒有構造方法。
  • 成員方法:
    有成員方法,而且都是抽象的。
    預設修飾符:public abstract

程式碼

參考上邊抽象類的程式碼,現在如果我想讓狗可以緝毒,那麼我該怎麼做?那我是不是定義一個緝毒的介面(規範),讓Dog類去接入實現就可以了。下邊看程式碼

```java
//抽象動物類
public abstract class Animal {
	//抽象方法
	public abstract void eat();
	
	public void sleep() {
		System.out.println("睡覺");
	}
}

//貓類
public class Cat extends Animal{
	//重寫父類中的方法
	@Override
	public void eat() {
		// TODO 自動生成的方法存根
		System.out.println("貓吃魚");
	}
	
}

//狗類
public abstract class Dog extends Animal implements AntiDrug{
	//子類沒有重寫父類(抽象類)中的所有方法,所以它需要為抽象類
	@Override
	public void antiDrug(){
	System.out.println("狗能緝毒了");
	}
}


//測試類
public class AnimalDemo {
	public static void main(String[] args) {
		//建立物件
		/*Animal a = new Animal();//抽象類不能例項化
*/		
		//按照多型的形式例項化抽象類
		Animal a = new Cat();
		a.eat();
		a.sleep();
	}
}

public interface AntiDrug{//定義緝毒的介面
	public abstract void antiDrug();
} 

抽象類與介面的區別


成員區別:

抽象類:
      以是變數,也可以是常量
      有構造方法
      成員方法可以是抽象方法,也可以是非抽象的方法
介面:
      成員變數只能是常量
      沒有構造方法
      成員方法只能是抽象方法

關係區別:

類與類:繼承關係,只能單繼承,不過可以多層繼承
類與介面:實現關係,可以單實現,也可以多實現
介面與介面:繼承關係,可以單繼承,也可以多繼承

設計理念的區別:

1、 抽象層次不同。抽象類是對類抽象,而介面是對行為的抽象。抽象類是對整個類整體進行抽象,包括屬性、行為,但是介面卻是對類區域性(行為)進行抽象。
2、 跨域不同。抽象類所跨域的是具有相似特點的類,而介面卻可以跨域不同的類。我們知道抽象類是從子類中發現公共部分,然後泛化成抽象類,子類繼承該父類即可,但是介面不同。實現它的子類可以不存在任何關係,共同之處。例如貓、狗可以抽象成一個動物類抽象類,具備叫的方法。鳥、飛機可以實現飛Fly介面,具備飛的行為,這裡我們總不能將鳥、飛機共用一個父類吧!所以說抽象類所體現的是一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在"is-a" 關係,即父類和派生類在概念本質上應該是相同的。對於介面則不然,並不要求介面的實現者和介面定義在概念本質上是一致的, 僅僅是實現了介面定義的契約而已。
3、 設計層次不同。對於抽象類而言,它是自下而上來設計的,我們要先知道子類才能抽象出父類,而介面則不同,它根本就不需要知道子類的存在,只需要定義一個規則即可,至於什麼子類、什麼時候怎麼實現它一概不知。比如我們只有一個貓類在這裡,如果你這是就抽象成一個動物類,是不是設計有點兒過度?我們起碼要有兩個動物類,貓、狗在這裡,我們在抽象他們的共同點形成動物抽象類吧!所以說抽象類往往都是通過重構而來的!但是介面就不同,比如說飛,我們根本就不知道會有什麼東西來實現這個飛介面,怎麼實現也不得而知,我們要做的就是事前定義好飛的行為介面。所以說抽象類是自底向上抽象而來的,介面是自頂向下設計出來的。

為了更好的闡述他們之間的區別,下面將使用一個例子來說明。

我們有一個Door的抽象概念,它具備兩個行為open()和close(),此時我們可以定義通過抽象類和介面來定義這個抽象概念:

抽象類

abstract class Door{
    abstract void open();
    abstract void close()}

介面

interface Door{
    void open();
    void close();
}

至於其他的具體類可以通過使用extends使用抽象類方式定義Door或者Implements使用介面方式定義Door,這裡發現兩者並沒有什麼很大的差異。
但是現在如果我們需要門具有報警的功能,那麼該如何實現呢?

解決方案一:給Door增加一個報警方法:clarm();

abstract class Door{
    abstract void open();
    abstract void close();
    abstract void alarm();
}

或者

interface Door{
    void open();
    void close();
    void alarm();
}

這種方法違反了物件導向設計中的一個核心原則 ISP (Interface Segregation Principle)1,在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方 法混在了一起。這樣引起的一個問題是那些僅僅依賴於Door這個概念的模組會因為"報警器"這個概念的改變而改變,反之依然。

解決方案二

既然open()、close()和alarm()屬於兩個不同的概念,那麼我們依據ISP原則將它們分開定義在兩個代表兩個不同概念的抽象類裡面,定義的方式有三種:

  1、兩個都使用抽象類來定義。

  2、兩個都使用介面來定義。

  3、一個使用抽象類定義,一個是用介面定義。

  由於java不支援多繼承所以第一種是不可行的。後面兩種都是可行的,但是選擇何種就反映了你對問題域本質的理解。

如果選擇第二種都是介面來定義,那麼就反映了兩個問題:
1、我們可能沒有理解清楚問題域,AlarmDoor在概念本質上到底是門還報警器。
2、如果我們對問題域的理解沒有問題,比如我們在分析時確定了AlarmDoor在本質上概念是一致的,那麼我們在設計時就沒有正確的反映出我們的設計意圖。因為你使用了兩個介面來進行定義,他們概念的定義並不能夠反映上述含義。

第三種,如果我們對問題域的理解是這樣的:AlarmDoor本質上Door,但同時它也擁有報警的行為功能,這個時候我們使用第三種方案恰好可以闡述我們的設計意圖。AlarmDoor本質是們,所以對於這個概念我們使用抽象類來定義,同時AlarmDoor具備報警功能,說明它能夠完成報警概念中定義的行為功能,所以alarm可以使用介面來進行定義。如下:

abstract class Door{
    abstract void open();
    abstract void close();
}

interface Alarm{
    void alarm();
}
 
class AlarmDoor extends Door implements Alarm{
    void open(){}
    void close(){}
    void alarm(){}
}

這種實現方式基本上能夠明確的反映出我們對於問題領域的理解,正確的揭示我們的設計意圖。其實抽象類表示的是"is-a"關係,介面表示的是"like-a"關係,大家在選擇時可以作為一個依據,當然這是建立在對問題領域的理解上的,比如:如果我們認為AlarmDoor在概念本質上是報警器,同時又具有Door的功能,那麼上述的定義方式就要反過來了。

參考文章:https://blog.csdn.net/chenssy/article/details/12858267


  1. ISP(Interface Segregation Principle):物件導向的一個核心原則。它表明使用多個專門的介面比使用單一的總介面要好。 一個類對另外一個類的依賴性應當是建立在最小的介面上的。一個介面代表一個角色,不應當將不同的角色都交給一個介面。沒有關係的介面合併在一起,形成一個臃腫的大介面,這是對角色和介面的汙染。 ↩︎

相關文章