《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,注意抽象方法中只有方法的聲明,沒有方法的實現,即沒有大括號({})部分。

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

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

//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之後的接口的新變化。熟悉抽象類和接口的區別。

配套視頻

http://edu.51cto.com/topic/1246.html

配套源代碼

http://www.zhijieketang.com/group/5

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

  1. 進入百度閱讀電子書

  2. 進入圖靈社區電子書
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章