1. 程式人生 > >《Java從小白到大牛》之第13章 抽象類與介面

《Java從小白到大牛》之第13章 抽象類與介面

《Java從小白到大牛》紙質版已經上架了!!!
Java從小白到大牛書皮

設計良好的軟體系統應該具備“可複用性”和“可擴充套件性”,能夠滿足使用者需求的不斷變更。使用抽象類和介面是實現“可複用性”和“可擴充套件性”重要的設計手段。

抽象類

Java語言提供了兩種類:一種是具體類;另一種是抽象了。前面章節接觸的類都是具體類。這一節介紹一下抽象類。

抽象類概念 {#-0}

在13.4.1節介紹多型時候,使用過幾何圖形類示例,其中Figure(幾何圖形)類中有一個onDraw(繪圖)方法,Figure有兩個子類Ellipse(橢圓形)和Triangle(三角形),Ellipse和Triangle覆蓋onDraw方法。

作為父類Figure(幾何圖形)並不知道在實際使用時有多少個子類,目前有橢圓形和三角形,那麼不同的使用者需求可能會有矩形或圓形等其他幾何圖形,而onDraw方法只有確定是哪一個子類後才能具體實現。Figure中的onDraw方法不能具體實現,所以只能是一個抽象方法。在Java中具有抽象方法的類稱為“抽象類”,Figure是抽象類,其中的onDraw方法是抽象方法。如圖13-1所示類圖中Figure是抽象類,Ellipse和Triangle是Figure子類實現Figure的抽象方法onDraw。
圖13-1 抽象類幾何圖形類圖

提示 在UML類圖抽象類和抽象方法字型是斜體的,見圖13-1所示中的Figure類和onDraw方法都是斜體的。

抽象類宣告和實現 {#-1}

在Java中抽象類和抽象方法的修飾符是abstract,宣告抽象類Figure示例程式碼如下:

//Figure.java檔案

package com.a51work6;

public abstract class Figure {// 繪製幾何圖形方法

public abstract void onDraw();}

程式碼第①行是宣告抽象類,在類前面加上abstract修飾符。程式碼第②行宣告抽象方法,方法前面的修飾符也是abstract,注意抽象方法中只有方法的宣告,沒有方法的實現,即沒有大括號({})部分。

注意 如果一個方法被宣告為抽象的,那麼這個類也必須宣告為抽象的。而一個抽象類中,可以有0n個抽象方法,以及0n具體方法。

設計抽象方法目的就是讓子類來實現的,否則抽象方法就沒有任何意義,實現抽象類示例程式碼如下:

//Ellipse.java檔案

package com.a51work6;

//幾何圖形橢圓形

public class Ellipse extends Figure {

//繪製幾何圖形方法

@Override

public void onDraw() {

System.out.println("繪製橢圓形...");

}

}

//Triangle.java檔案
package com.a51work6; //幾何圖形三角形 public class Triangle extends Figure { // 繪製幾何圖形方法 @Override public void onDraw() { System.out.println("繪製三角形..."); } }

上述程式碼聲明瞭兩個具體類Ellipse和Triangle,它們實現(覆蓋)了抽象類Figure的抽象方法onDraw。

呼叫程式碼如下:

//HelloWorld.java檔案

package com.a51work6;

public class HelloWorld {

public static void main(String[] args) {

// f1變數是父類型別,指向子類例項,發生多型

Figure f1 = new Triangle();

f1.onDraw();

// f2變數是父類型別,指向子類例項,發生多型

Figure f2 = new Ellipse();

f2.onDraw();

}

}

上述程式碼中例項化兩個具體類Triangle和Ellipse,物件f1和f2是Figure引用型別。

注意 抽象類不能被例項化,只有具體類才能被例項化。

使用介面

比抽象類更加抽象的是介面,在介面中所有的方法都是抽象的。

提示 Java 8之後介面中新增加了預設方法,因此“介面中所有的方法都是抽象的”這個提法在Java 8之後是有待商榷。

介面概念 {#-0}

其實13.1.1節抽象類Figure可以更加徹底,即Figure介面,介面中所有方法都是抽象的,而且介面可以有成員變數。將13.1.1節幾何圖形類改成介面後,類圖如圖13.2所示。

圖13-2 介面幾何圖形類圖

提示 在UML類圖中介面的圖示是“I”,見圖13-2所示中的Figure介面。類的圖示是“C”,見圖13-2所示中的Triangle介面。

介面宣告和實現 {#-1}

在Java中介面的宣告使用的關鍵字是interface,宣告介面Figure示例程式碼如下:

//Figure.java檔案

package com.a51work6;

public interface Figure {//介面中靜態成員變數

String name = "幾何圖形";//省略public static final ②

// 繪製幾何圖形方法

void onDraw(); //省略public ③

}

程式碼第①行是宣告Figure介面,宣告介面使用interface關鍵字,interface前面的修飾符是public或省略。public是公有訪問級別,可以在任何地方訪問。省略是預設訪問級別,只能在當前包中訪問。

程式碼第②行宣告介面中的成員變數,在介面中成員變數都靜態成員變數,即省略了public static final修飾符。程式碼第③行是宣告抽象方法,即省略了public關鍵字。

某個類實現介面時,要在宣告時使用implements關鍵字,當實現多個介面之間用逗號(,)分隔。實現介面時要實現介面中宣告的所有方法。

實現介面Figure示例程式碼如下:

//Ellipse.java檔案

package com.a51work6.imp;

import com.a51work6.Figure;

//幾何圖形橢圓形

public class Ellipse implements Figure {

//繪製幾何圖形方法

@Override

public void onDraw() {

System.out.println("繪製橢圓形...");

}

}

//Triangle.java檔案

package com.a51work6.imp;

import com.a51work6.Figure;

//幾何圖形三角形

public class Triangle implements Figure {

// 繪製幾何圖形方法

@Override

public void onDraw() {

System.out.println("繪製三角形...");

}

}

上述程式碼聲明瞭兩個具體類Ellipse和Triangle,它們實現了介面Figure中的抽象方法onDraw。

呼叫程式碼如下:

//HelloWorld.java檔案

import com.a51work6.imp.Ellipse;

import com.a51work6.imp.Triangle;

public class HelloWorld {

public static void main(String[] args) {

// f1變數是父類型別,指向子類例項,發生多型

Figure f1 = new Triangle();

f1.onDraw();

System.out.println(f1.name); ①

System.out.println(Figure.name);// f2變數是父類型別,指向子類例項,發生多型

Figure f2 = new Ellipse();

f2.onDraw();

}

}

上述程式碼中例項化兩個具體類Triangle和Ellipse,物件f1和f2是Figure介面引用型別。介面Figure中聲明瞭成員變數,它是靜態成員變數,程式碼第①行和第②行是訪問name靜態變數。

注意 介面與抽象類一樣都不能被例項化。

介面與多繼承 {#-2}

在C++語言中一個類可以繼承多個父類,但這會有潛在的風險,如果兩個父類在有相同的方法,那麼子類如何繼承哪一個方法呢?這就是C++多繼承所導致的衝突問題。

在Java中只允許繼承一個類,但可實現多個介面。通過實現多個介面方式滿足多繼承的設計需求。如果多個介面中即便有相同方法,它們也都是抽象的,子類實現它們不會有衝突。

圖13-3所示是多繼承類圖,其中的有兩個介面InterfaceA和InterfaceB,從類圖中可以見兩個介面中都有一個相同的方法void methodB()。AB實現了這兩個介面,繼承了Object父類。

圖13-3 多繼承類圖

介面InterfaceA和InterfaceB程式碼如下:

//InterfaceA.java檔案

package com.a51work6;

public interface InterfaceA {

void methodA();

void methodB();

}

//InterfaceB.java檔案

package com.a51work6;

public interface InterfaceB {

void methodB();

void methodC();

}

從程式碼中可見兩個介面都有兩個方法,其中方法methodB()完全相同。

實現介面InterfaceA和InterfaceB的AB類程式碼如下:

//AB.java檔案

package com.a51work6.imp;

import com.a51work6.InterfaceA;

import com.a51work6.InterfaceB;

public class AB extends Object implements InterfaceA, InterfaceB {@Override

public void methodC() {

}

@Override

public void methodA() {

}

@Override

public void methodB() {}

}

在AB類中的程式碼第②行實現methodB()方法。注意在AB類宣告時,實現兩個介面,介面之間使用逗號(,)分隔,見程式碼第①行。

介面繼承 {#-3}

Java語言中允許介面和介面之間繼承。由於介面中的方法都是抽象方法,所以繼承之後也不需要做什麼,因此介面之間的繼承要比類之間的繼承簡單的多。如同4-4所示,其中InterfaceB繼承了InterfaceA,在InterfaceB中還覆蓋了InterfaceA中的methodB()方法。ABC是InterfaceB介面的實現類,從圖可見ABC需要實現InterfaceA和InterfaceB介面中的所有方法。

圖13-4 介面繼承類圖

介面InterfaceA和InterfaceB程式碼如下:

//InterfaceA.java檔案

package com.a51work6;

public interface InterfaceA {

void methodA();

void methodB();

}

//InterfaceB.java檔案

package com.a51work6;

public interface InterfaceB extends InterfaceA {

@Override

void methodB();

void methodC();

}

InterfaceB繼承了InterfaceA,宣告時也使用extends關鍵字。InterfaceB 中的methodB()覆蓋了InterfaceA,事實上在介面中覆蓋方法,並沒有實際意義,因為它們都是抽象的,都是留給子類實現的。

實現介面InterfaceB的ABC類程式碼如下:

//ABC.java檔案

package com.a51work6.imp;

import com.a51work6.InterfaceB;

public class ABC implements InterfaceB {

@Override

public void methodA() {

}

@Override

public void methodB() {

}

@Override

public void methodC() {

}

}

ABC類實現了介面InterfaceB,事實上是實現InterfaceA和InterfaceB中所有方法,相當於同時實現InterfaceA和InterfaceB介面。

Java 8新特性預設方法和靜態方法 {#java-8}

在Java 8之前,儘管Java語言中介面已經非常優秀了,但相比其他面向物件的語言而言Java介面存在如下不足之處:

  1. 不能可選實現方法,介面的方法全部是抽象的,實現介面時必須全部實現介面中方法,哪怕是有些方法並不需要,也必須實現。
  2. 沒有靜態方法。

針對這些問題,Java 8在介面中提供了宣告預設方法和靜態方法的能力。介面示例程式碼如下:

//InterfaceA.java檔案

package com.a51work6;

public interface InterfaceA {

void methodA();

String methodB();

// 預設方法

default int methodC() {

return 0;

}

// 預設方法

default String methodD() {

return "這是預設方法...";

}

// 靜態方法

static double methodE() {

return 0.0;

}

}

在介面InterfaceA中聲明瞭兩個抽象方法methodA和methodB,兩個預設方法methodC和methodD,還有聲明瞭靜態方法methodE。介面中的預設方法類似於類中具體方法,給出了具體實現,只是方法修飾符是default。介面中靜態方法類似於類中靜態方法。

實現介面示例程式碼如下:

//ABC.java檔案

package com.a51work6.imp;

import com.a51work6.InterfaceA;

public class ABC implements InterfaceA {

@Override

public void methodA() {

}

@Override

public String methodB() {

return "實現methodB方法...";

}

@Override

public int methodC() {

return 500;

}

}

實現介面時介面中原有的抽象方法在實現類中必須實現。預設方法可以根據需要有選擇實現(覆蓋)。靜態方法不需要實現,實現類中不能擁有介面中的靜態方法。

上述程式碼中ABC類實現了InterfaceA介面,InterfaceA介面中的兩個預設方法ABC只是實現(覆蓋)了methodB。

呼叫程式碼如下:

//HelloWorld.java檔案

package com.a51work6.imp;

import com.a51work6.InterfaceA;

public class HelloWorld {

public static void main(String[] args) {

//宣告介面型別,物件是實現類,發生多型

InterfaceA abc = new ABC();

// 訪問實現類methodB方法

System.out.println(abc.methodB());

// 訪問預設方法methodC

System.out.println(abc.methodC());// 訪問預設方法methodD

System.out.println(abc.methodD());// 訪問InterfaceA靜態方法methodE

System.out.println(InterfaceA.methodE());}

}

執行結果:

實現methodB方法...

500

這是預設方法...

0.0

從執行結果可見,程式碼第①行呼叫預設方法methodC,是呼叫類AB中的實現。程式碼第②行呼叫預設方法methodD,是呼叫介面InterfaceA中的實現。程式碼第③行呼叫介面靜態方法,只能通過介面名(InterfaceA)呼叫,不能通過實現類ABC呼叫,可以這樣理解介面中宣告的靜態方法與其他實現類沒有任何關係。

抽象類與介面區別

經過前面的學習,廣大讀者應該對於抽象類和介面所瞭解,可能會有這樣的疑問抽象類和介面有什麼區別?本節就回答這個問題。

歸納抽象類與介面區別如下:

  1. 介面支援多繼承,而抽象類(包括具體類)只能繼承一個父類。
  2. 介面中不能有例項成員變數,介面所宣告的成員變數全部是靜態常量,即便是變數不加public static final修飾符也是靜態常量。抽象類與普通類一樣各種形式的成員變數都可以宣告。
  3. 介面中沒有包含構造方法,由於沒有例項成員變數,也就不需要構造方法了。抽象類中可以有例項成員變數,也需要構造方法。
  4. 抽象類中可以宣告抽象方法和具體方法。Java 8之前介面中只有抽象方法,而Java 8之後介面中也可以宣告具體方法,具體方法通過宣告預設方法實現。

提示 學習了介面預設方法後,有些讀者還會有這樣的疑問,Java 8之後介面可以宣告抽象方法和具體方法,這就相當於抽象類一樣了嗎?在多數情況下介面不能替代抽象類,例如當需要維護一個物件的資訊和狀態時只能使用抽象類,而介面不行,因為維護一個物件的資訊和狀態需要儲存在例項成員變數中,而介面中不能宣告例項成員變數。

本章小結

通過對本章的學習,讀者可以瞭解抽象類和介面的概念,掌握如何宣告抽象類和介面,如何實現抽象類和介面。瞭解Java 8之後的介面的新變化。熟悉抽象類和介面的區別。

配套視訊

配套原始碼

與本書免費版對應的還有一個收費版本: