- Java 從入門到項目實踐(超值版)
- 聚慕課教育研發中心編著
- 10860字
- 2019-07-30 17:29:15
第6章 Java最重要的部分——抽象類與接口
◎ 本章教學微視頻:19個 107分鐘
學習指引
面向對象編程的過程是一個逐步抽象的過程。接口是比抽象類更高層的抽象,它是對行為的抽象;而抽象類是對一種事物的抽象,即對類的抽象。本章介紹Java的抽象類與接口的相關知識,主要內容包括抽象類和抽象方法、接口的基本知識、接口的多態等。
重點導讀
- 掌握抽象類和抽象方法的應用。
- 掌握Java接口的基本知識。
- 掌握Java接口的高級應用。
- 掌握抽象類和接口的應用實例。
- 掌握Java集合框架的使用方法。
6.1 抽象類和抽象方法
在面向對象的概念中,所有的對象都是通過類來描繪的;但是反過來,并不是所有的類都是用來描繪對象的,若一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象方法指一些只有方法聲明,而沒有具體方法體的方法。抽象方法一般存在于抽象類或接口中。
6.1.1 認識抽象類

假設要編寫一個計算圓、三角形和矩形的面積與周長的程序。若按前面所學的方式編程,就必須定義4個類:圓類、三角形類、矩形類和使用前3個類的公共類,它們之間沒有繼承關系。程序寫好后雖然能執行,但從程序的整體結構上看,前3個類之間的許多共同屬性和操作在程序中沒有很好地被利用,需要重復編寫代碼,降低了程序的開發效率,且使出現錯誤的機會增加。
仔細分析上面例子中的前3個類,可以看到這3個類都要計算面積與周長,雖然公式不同,但目標相同。因此,可以為這3個類抽象出一個父類,在父類里定義圓、三角形和矩形3個類共同的成員屬性及成員方法。把計算面積與周長的成員方法名放在父類中說明,再將具體的計算公式在子類中實現。
這樣,通過父類就大概知道子類所要完成的任務,而且,這些方法還可以應用于求解梯形、平行四邊形等其他圖形的面積與周長。這種結構就是抽象類的概念。
Java程序用抽象類(abstract class)來實現自然界的抽象概念。抽象類的作用在于將許多有關的類組織在一起,提供一個公共的類,即抽象類。而那些被它組織在一起的具體的類將作為它的子類由它派生出來。抽象類刻畫了公有行為的特征,并通過繼承機制傳遞給它的派生類。
抽象類是它的所有子類的公共屬性的集合,是包含一個或多個抽象方法的類。使用抽象類的一大優點就是可以充分利用這些公共屬性來提高開發和維護程序的效率。
6.1.2 定義抽象類

與普通類相比,抽象類要使用abstract關鍵字聲明。普通類是一個完善的功能類,可以直接產生實例化對象,并且在普通類中可以包含構造方法、普通方法、static方法、常量和變量等內容。而抽象類是在普通類的結構里面增加抽象方法的內容。
【例6-1】(實例文件:ch06\Chap6.1.txt)定義抽象類應用實例。
public abstract class Animal {//定義一個抽象類 //抽象方法沒有方法體,用abstract修飾 public abstract void shout(); }
本例中,定義了一個抽象類Animal,有一個抽象方法shout(),注意shout()方法沒有方法體,直接以分號結束。抽象類的使用原則如下:
- 抽象方法必須為public或者protected(因為如果為private,則不能被子類繼承,子類便無法實現該方法),默認為public。
- 抽象類不能直接實例化,需要依靠子類采用向上轉型的方式處理。
- 抽象類必須有子類,使用extends繼承,一個子類只能繼承一個抽象類。
- 子類如果不是抽象類,則必須重寫抽象類中的全部抽象方法(如果子類沒有實現父類的抽象方法,則必須將子類也定義為抽象類)。
- 抽象類不能使用final關鍵字聲明,因為抽象類必須有子類,而final定義的類不能有子類。
【例6-2】(實例文件:ch06\Chap6.2.txt)子類繼承抽象類應用實例(每個類均為單獨的文件)。

本例中,定義了一個子類Dog繼承抽象類Animal,并實現了抽象方法shout(),定義了shout()顯示狗的叫聲。
【例6-3】(實例文件:ch06\Chap6.3.txt)抽象類通過子類向上轉型實例化(每個類均為單獨的文件)。

程序運行結果如圖6-1所示。

圖6-1 程序運行結果
抽象類是不能直接實例化的,因此Animal a1 = new Animal();在編譯時會報錯,那么如何實例化抽象類呢?答案是需要依靠子類采用向上轉型的方式來實例化。本例中通過Animal的子類Dog向上轉型來實例化:Animal a1 = new Dog();,a1擁有了Dog類重寫的shout()方法。總結如下:
- 抽象類繼承子類時必須重寫方法,而普通類可以有選擇地決定是否需要重寫方法。
- 抽象類實際上比普通類多了一些抽象方法,其他組成部分和普通類完全一樣。
- 普通類對象可以直接實例化,但抽象類的對象必須經過向上轉型之后才可以得到。
雖然一個類的子類可以繼承任意的一個普通類,可是從開發的實際要求來講,普通類盡量不要繼承另外一個普通類,而應該繼承抽象類。
【例6-4】(實例文件:ch06\Chap6.4.txt)抽象類實例(每個類均為單獨的文件)。

程序運行結果如圖6-2所示。

圖6-2 抽象類的應用實例
抽象類在應用的過程中,需要注意以下幾點:
- 抽象類不能被實例化,如果被實例化,就會報錯,編譯無法通過。只有抽象類的非抽象子類才可以創建對象。
- 抽象類中不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
- 抽象類中的抽象方法只是聲明,不包含方法體,也就是不給出方法的具體實現(即方法的具體功能)。
- 構造方法和類方法(用static修飾的方法)不能聲明為抽象方法。
- 抽象類的子類必須給出抽象類中的抽象方法的具體實現,除非該子類也是抽象類。
6.1.3 典型應用實例

抽象類的一個典型應用就是模板設計模式。假設現在有3類不同的對象:機器人、人、貓,這3類對象有不同的行為,分別如下:
- 機器人:充電,工作,關機。
- 人:吃飯,工作,睡覺。
- 貓:進食,逮老鼠,睡覺。
下面編寫一個程序,實現3種不同事物的不同行為。
【例6-5】(實例文件:ch06\Chap6.5.txt)抽象類應用:模板設計模式(每個類均為單獨的文件)。


程序運行結果如圖6-3所示。

圖6-3 模板設計模式實例
6.1.4 抽象方法

Java語言中的抽象方法用關鍵字abstract修飾,這種方法只聲明返回的數據類型、方法名稱和所需的參數,沒有方法體,即抽象方法只需要聲明而不需要實現。
1.聲明抽象方法
如果一個類包含抽象方法,那么該類必須是抽象類。任何子類必須重寫父類的抽象方法,否則子類自身必須聲明為抽象類。聲明一個抽象類的語法格式如下:
abstract 返回類型 方法名([參數表]);
注意:抽象方法沒有定義方法體,方法名后面直接跟一個分號,而不是花括號。
2.抽象方法的實現
繼承抽象類的子類必須重寫父類的抽象方法,否則,該子類也必須聲明為抽象類。最終,必須由子類實現父類的抽象方法,否則,從最初的父類到最終的子類都不能用來實例化對象。下面通過一個例子介紹子類如何重寫父類的抽象方法。
【例6-6】(實例文件:ch06\Chap6.6.txt)定義子類Apple繼承抽象類Fruit,重寫父類中的方法。
public class Apple extends Fruit{ public Apple(){ color = "紅色"; } public void color(){ System.out.println("蘋果是:" + color); } public static void main(String[] args) { Apple a = new Apple(); a.color(); } }
程序運行結果如圖6-4所示。在本例中,定義繼承抽象類Fruit的子類Apple,它實現了父類的抽象方法color(),并重寫了自己的構造方法。在程序的main()方法中創建子類對象a,a調用子類Apple實現的抽象方法color()。

圖6-4 抽象方法的實現
6.2 接口概述
接口(interface)是Java所提供的另一種重要的技術,接口是一種特殊的類,它的結構和抽象類非常相似,可以認為是抽象類的一種變體。
6.2.1 接口聲明

接口是比抽象類更高的抽象,它是一個完全抽象的類,即抽象方法的集合。接口使用關鍵字interface來聲明,語法格式如下:
[public] interface 接口名稱 [extends 其他的類名]{ [public][static][final] 數據類型 成員名稱=常量值; [public][static][abstract] 返回值 抽象方法名(參數列表); }
接口中的方法是不能在接口中實現的,只能由實現接口的類來實現。一個類可以通過關鍵字implements來實現。如果實現類沒有實現接口中的所有抽象方法,那么該類必須聲明為抽象類。下面是接口聲明的一個簡單例子。
【例6-7】(實例文件:ch06\Chap6.7.txt)接口聲明。
public interface Shape { public double area(); //計算面積 public double perimeter(); //計算周長 }
使用關鍵字interface聲明了一個接口Shape,并在接口內定義了兩個抽象方法area()和perimeter()。接口有以下特性:
- 接口中也有變量,但是接口會隱式地指定為public static final變量,并且只能是public,用private修飾會報編譯錯誤。
- 接口中的抽象方法具有public和abstract修飾符,也只能是這兩個修飾符,其他修飾符都會報錯。
- 接口是通過類來實現的。
- 一個類可以實現多個接口,多個接口之間使用逗號(,)隔開。
- 接口可以被繼承,被繼承的接口必須是另一個接口。
6.2.2 實現接口

當類實現接口的時候,類要實現接口中所有的方法,否則類必須聲明為抽象的類。類使用implements關鍵字實現接口。在類聲明中,implements關鍵字放在class聲明后面。實現一個接口的語法如下:
class 類名稱 implements 接口名稱[,其他接口]{ … }
下面給出一個實現接口的實例。
【例6-8】(實例文件:ch06\Chap6.8.txt)實現接口(每個類均為單獨的文件)。

在本例中,定義了兩個類Circle和Rectangle,它們分別實現了接口Shape,并實現了接口定義的兩個抽象方法,用來計算面積和周長。在實現接口的時候,要注意以下規則:
- 一個類可以同時實現多個接口。
- 一個類只能繼承一個類,但是能實現多個接口。
- 一個接口能繼承另一個接口,這和類之間的繼承比較相似。
- 重寫接口中聲明的方法時,需要注意以下規則。
- ◆ 類在實現接口的方法時,不能拋出強制性異常,只能在接口中,或者繼承接口的抽象類中拋出該強制性異常。
- ◆ 類在重寫方法時要保持一致的方法名,并且應該保持相同或者相兼容的返回值類型。
- ◆ 如果實現接口的類是抽象類,那么就沒必要實現該接口的方法。
6.2.3 接口默認方法

Java提供了接口默認方法。即允許接口中可以有實現方法,使用default關鍵字在接口修飾一個非抽象的方法,這個特征又叫擴展方法。
【例6-9】(實例文件:ch06\Chap6.9.txt)接口默認方法。
public interface InterfaceNew { public double method(int a); public default void test() { System.out.println("Java 8接口新特性"); } }
在本例中,定義了接口InterfaceNew,除了聲明抽象方法method()外,還定義了使用default關鍵字修飾的實現方法test(),實現了InterfaceNew接口的子類只需實現一個calculate()方法即可,test()方法在子類中可以直接使用。
6.2.4 接口與抽象類

接口的結構和抽象類非常相似,也具有數據成員與抽象方法,但它又與抽象類不同。下面詳細介紹接口與抽象類的異同。
1.接口與抽象類的相同點
接口與抽象類存在一些相同的特性,具體如下:
- 都可以被繼承。
- 都不能被直接實例化。
- 都可以包含抽象方法。
- 派生類必須實現未實現的方法。
2.接口與抽象類的不同點
接口與抽象類還有一些不同之處,具體如下:
- 接口支持多繼承,抽象類不能實現多繼承。
- 一個類只能繼承一個抽象類,但可以實現多個接口。
- 接口中的成員變量只能是public static final類型的,抽象類中的成員變量可以是各種類型的。
- 接口只能定義抽象方法;抽象類既可以定義抽象方法,也可以定義實現的方法。
- 接口中不能含有靜態代碼塊以及靜態方法(用static修飾的方法),抽象類可以有靜態代碼塊和靜態方法。
6.3 接口的高級應用
接口在Java中是最重要的概念之一,它可以被理解為一種特殊的類,是由全局常量和公共的抽象方法所組成的。需要注意的是,在接口中的抽象方法必須定義為public訪問權限,這是不可更改的。
6.3.1 接口的多態

Java中沒有多繼承,一個類只能有一個父類。而繼承的表現就是多態,一個父類可以有多個子類,而在子類里可以重寫父類的方法,這樣每個子類里重寫的代碼不一樣,自然表現形式就不一樣。
用父類的變量去引用不同的子類,在調用這個相同的方法的時候得到的結果和表現形式就不一樣了,這就是多態,調用相同的方法會有不同的結果。下面給出一個實例。
【例6-10】(實例文件:ch06\Chap6.10.txt)接口的多態,基于實現接口的實例。

運行結果如圖6-5所示。
在本例中,Shape是一個接口,沒有辦法實例化對象,但可以用Circle類和Rectangle類來實例化對象,也就實現了接口的多態。實例化產生的對象s1和s2擁有同名的方法,但各自實現的功能卻不一樣。根據實現接口的類中重寫的方法,實現了用同一個方法計算不同圖形的面積和周長的功能。

圖6-5 接口的多態應用實例
6.3.2 適配接口

在實現一個接口時,必須實現該接口的所有方法,這樣有時比較浪費,因為并不是所有的方法都是我們需要的,有時只需要使用其中的一些方法。為了解決這個問題,引入了接口的適配器模式,借助于一個抽象類來實現該接口所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯系。寫一個類,繼承該抽象類,再重寫需要的方法就行。
【例6-11】(實例文件:ch06\Chap6.11.txt)適配接口(每個類均為單獨的文件)。

程序運行結果如圖6-6所示。

圖6-6 適配接口應用實例
在本例中,首先定義了一個接口InterfaceAdapter,并定義了兩個抽象方法email()和sms()。然后定義了一個抽象類Wrapper,并實現了兩個抽象方法,但方法體為空。定義了一個類S1,重寫了email()方法。這樣寫的好處是,定義類時不需要直接實現接口InterfaceAdapter并實現定義的兩個方法,而只需要實現并重寫email()方法即可。
6.3.3 嵌套接口

在Java語言中,接口可以嵌套在類或其他接口中。由于Java中在interface內是不可以嵌套class的,所以接口的嵌套共有兩種方式:class內嵌套interface、interface內嵌套interface。
1.class內嵌套interface
這時接口可以是public、private和package。重點在private上,被定義為私有的接口只能在接口所在的類中實現。可以被實現為public的類也可以被實現為private。當被實現為public時,只能在自身所在的類內部使用。只能夠實現接口中的方法,在外部不能像正常類那樣上傳為接口類型。
2.interface內嵌套interface
由于接口的元素必須是public的,所以被嵌套的接口自動就是public的,而不能定義成private的。在實現這種嵌套時,不必實現被嵌套的接口。
【例6-12】(實例文件:ch06\Chap6.12.txt)嵌套接口舉例(每個類均為單獨的文件)。

本例中,語句A.D ad = a.getD();和a.getD().f();產生編譯錯誤,這是因為D是A的私有接口,不能在外部被訪問。語句A.DImp2 di2 = a.getD();的錯誤是因為getD()方法的返回值類型為D,不能自動向下轉型為DImp2類型。
6.3.4 接口回調

接口回調是指可以把使用某一接口的類創建的對象的引用賦給該接口聲明的接口變量,那么該接口變量就可以調用被類實現的接口的方法。實際上,當接口變量調用被類實現的接口中的方法時,就是通知相應的對象調用接口的方法,這一過程稱為對象功能的接口回調。下面看一個例子。
【例6-13】(實例文件:ch06\Chap6.13.txt)接口回調,基于實現接口的例子(每個類均為單獨的文件)。

程序運行結果如圖6-7所示。

圖6-7 接口回調應用實例
本例中定義了一個類Show,其中定義了一個方法print(),將Shape類型的變量作為參數。在測試時,實例化Show,并調用print()方法,將new Circle()和new Rectangle()作為實際參數,因此會調用不同的方法,結果顯示不同圖形的面積和周長。
6.4 抽象類和接口的實例
在介紹了抽象類和接口的聲明和使用以及抽象方法的使用后,本節舉例介紹抽象類與接口的使用。
6.4.1 抽象類的應用實例

抽象類本身是不能直接實例化的,因為其本身包含抽象方法。不過,抽象類可以通過對象的多態性來實例化,即抽象類通過子類進行實例化操作。那么可以將抽象類作為某個類型的模板,而具體的子類通過繼承該抽象類,重寫其中的方法來實現。下面看一個例子。
【例6-14】(實例文件:ch06\Chap6.14.txt)抽象類的實際應用(每個類均為單獨的文件)。

程序運行結果如圖6-8所示。

圖6-8 抽象類的應用
首先定義一個抽象類Person,定義一個抽象方法call(),然后定義兩個類Teacher和Student,分別繼承抽象類Person,并分別實現了方法call();定義類Lesson,定義方法lessonBegin(),使用Person類型的變量作為參數,在測試時實例化Lesson,并使用new Teacher()和new Student()作為實際參數。最后老師會說“同學們好!”,學生說“老師好!”。
6.4.2 接口的應用實例

接口的作用就是將方法名稱開放給用戶。接口與抽象類一樣,需要通過子類進行實例化操作。接口的實際應用更類似于定義標準。下面看一個例子。
【例6-15】(實例文件:ch06\Chap6.15.txt)接口的實際應用,以USB接口為例(每個類均為單獨的文件)。

程序運行結果如圖6-9所示。

圖6-9 接口的實際應用
首先定義接口USB,定義兩個抽象方法start()和stop(),然后定義兩個類Mouse和Keyboard,分別實現了接口USB,并實現了兩個方法start()和stop()。定義類MainBoard,定義方法plugIn(),使用USB類型的變量作為參數,在測試時實例化MainBorad,并使用new Mouse()和new Keyborad()作為實際參數,
另外,接口在實際編程中用途非常廣泛,在設計模式中也有很多應用,下面講解設計模式中的工廠模式。
【例6-16】(實例文件:ch06\Chap6.16.txt)接口應用:工廠模式。
interface Fruit { public void eat() ; } class Apple implements Fruit { public void eat() { System.out.println("吃蘋果。") ; } } class Orange implements Fruit { public void eat() { System.out.println("吃橘子。") ; } } class Factory1 { //此類不需要維護屬性的狀態 public static Fruit getInstance(String className) { if ("apple".equals(className)) { return new Apple() ; } if ("orange".equals(className)) { return new Orange() ; } return null ; } } public class Factory { public static void main(String args[]) { Fruit f = Factory1.getInstance(args[0]) ; //初始化參數 f.eat() ; } }
當args[0]為apple時,程序運行結果如圖6-10所示。

圖6-10 當args[0]為apple時程序運行結果
當args[0]為orange時,程序運行結果如圖6-11所示。

圖6-11 當args[0]為orange時程序運行結果
輸入參數的方法如下:選擇菜單欄中的Run→Run Configurations命令,如圖6-12所示。

圖6-12 選擇Run Configurations命令
打開如圖6-13所示的窗口,在Program arguments文本框中輸入apple,這樣就表示args[0]為apple,調試結果控制臺會顯示“吃蘋果”。如果將apple改為orange,則會顯示“吃橘子”。

圖6-13 Run Configurations對話框
本例中,首先定義了一個水果接口Fruit,其中定義了一個抽象方法eat(),然后定義了Apple類和Orange類分別實現了接口Fruit,并實現了抽象方法eat(),之后定義了類Factory1,最后定義類Factory,并根據參數內容實例化不同的子類。
根據args[0]的內容實例化不同的子類。如果為apple,實例化Apple類;如果為orange,則實例化Orange類。因此輸出的內容也不同。
6.5 Java的集合框架
在Java語言中有一個由設計優良的接口和類組成的Java集合框架,以方便程序員操作成批的數據或對象元素。本節詳細介紹集合框架的使用。
6.5.1 接口和實現類

Java語言中的集合框架就是一個類庫的集合,包含了實現集合框架的接口。集合框架就像一個容器,用來存儲Java類的對象。Java所提供的集合API都在java.util包中。集合框架結構如圖6-14所示。

圖6-14 集合框架結構
1.Java集合框架中的接口
在Java集合框架中提供了以下接口:
- Collection接口。該接口定義了存取一組對象的方法,是最基本的接口。
- Set接口。該接口繼承Collection接口,它包含的數據沒有順序且不可以重復。
- List接口。該接口繼承Collection接口,它包含的數據有順序且可以重復。
- Map接口。該接口是一個單獨的接口,不繼承Collection接口。它是一種把鍵對象和值對象進行關聯的容器,不可以包含重復的鍵。
2.Java集合框架中的實現類
在Java集合框架中提供了實現接口的以下類:
- HashSet。實現了Set接口,為無序集合,能夠快速定位一個元素。需要注意的是,存入HashSet中的對象必須實現HashCode()方法。
- TreeSet。不僅實現了Set接口,還實現了Sorted接口,可對集合進行自然排序。
- ArrayList。實現了List接口,為有序集合,它的大小可變并且可以像鏈表一樣被訪問。它是以數組的方式實現的List接口,允許快速隨機存取。
- LinkedList。實現了List接口,為有序集合,通過一個鏈表的形式實現List接口,提供最佳順序存取,適合插入和移除元素。由這個類定義的鏈表也可以像棧或隊列一樣被使用。
- HashMap。實現一個“鍵-值”映射的哈希表,通過鍵獲取值對象,沒有順序,通過get(key)方法來獲取value的值,允許存儲空對象,而且允許鍵是空的。不過,鍵只能有一個。
6.5.2 Collection接口

Collection接口是Set接口和List接口的父接口,是最基本的接口。Collection接口定義了對集合進行基本操作的一些通用方法。由于Set接口和List接口繼承自Collection接口,所以可以調用這些方法。Collection接口提供的主要方法如表6-1所示。
表6-1 Collection接口中的方法

在所有實現Collection接口的容器類中都有一個iterator()方法,此方法返回一個實現了Iterator接口的對象。Iterator對象稱作迭代器,方便實現對容器內元素的遍歷操作。
由于Collection是一個接口,不能直接實例化。下面的例子是通過ArrayList實現類來調用Collection接口的方法。
【例6-17】(實例文件:ch06\Chap6.17.txt)Collection接口方法的使用。

程序運行結果如圖6-15所示。

圖6-15 Collection接口方法的使用
在本例中,定義了接口List的實現類ArrayList的對象c和array,通過add()方法為兩個集合添加元素。
- 集合array調用isEmpty()方法,在集合array不為空的情況下,集合c調用addAll()方法,將集合array中的元素全部添加到集合c中。
- 集合c調用iterator()方法返回迭代器對象Iterator,通過while循環輸出集合c中的元素。iterator.hasNext()方法判斷迭代器中是否有下一個元素,如有則執行iterator.next()方法輸出元素。
- 集合c調用contains()方法判斷集合c是否包含指定的元素;調用removeAll()方法移出array集合的元素;調用toArray()方法將集合元素存放到數組str中,并通過for循環輸出數組元素。
注意:任何對象加入集合類后都自動轉變為Object類型,所以在取出的時候需要進行強制類型轉換。
6.5.3 List接口

List接口是Collection的子接口,實現List接口的容器類中的元素是有順序的,并且元素可以重復。List容器中的元素對應一個整數型的序號,記錄其在List容器中的位置,可以根據序號存取容器中的元素。List接口除了繼承Collection接口的方法外,又提供了一些方法,如表6-2所示。
表6-2 List接口中的方法

在Java API中提供的實現List接口的容器類有ArrayList、LinkedList等,它們是有序的容器類。使用哪個實現類要根據具體的場合來定。
1.ArrayList類
ArrayList類實現一個可變大小的數組,可以像鏈表一樣被訪問。它以數組的方式實現,允許快速隨機存取。它允許包含所有元素,包括null元素。每個ArrayList類實例都有一個容量(capacity),即存儲元素的數組大小,這個容量可以隨著不斷添加新元素而自動增加。
ArrayList類常用的構造方法有3種重載形式,具體如下:
(1)構造一個初始容量為10的空列表。
public ArrayList()
(2)構造一個指定初始容量的空列表。
public ArrayList(int initialCapacity)
(3)構造一個包含指定集合元素的列表,這些元素是按照該集合的迭代器返回它們的順序排列。
public ArrayList(Collection c)
【例6-18】(實例文件:ch06\Chap6.18.txt)ArrayList類的使用。
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ArrrayListTest { public static void main(String[] args) { ArrayList list = new ArrayList(); //創建初始容量為10的空列表 list.add("cat"); list.add("dog"); list.add("pig"); list.add("sheep"); list.add("pig"); System.out.println("---輸出集合中的元素---"); Iterator iterator = list.iterator(); while(iterator.hasNext()){ System.out.print(iterator.next()+" "); } System.out.println(); //替換指定索引處的元素 System.out.println("返回替換集合中索引是1的元素:" + list.set(1, "mouse")); iterator = list.iterator(); System.out.println("---元素替換后集合中的元素---"); while(iterator.hasNext()){ System.out.print(iterator.next()+" "); } System.out.println(); //獲取指定索引處的集合元素 System.out.println( "獲取集合中索引是2的元素:"+ list.get(2)); System.out.println("集合中第一次出現pig的索引:" + list.indexOf("pig")); System.out.println("集合中最后一次出現dog的索引:" + list.lastIndexOf("dog")); List l = list.subList(1, 4); iterator = l.iterator(); System.out.println("---新集合中的元素---"); while(iterator.hasNext()){ System.out.print(iterator.next()+" "); } } }
程序運行結果如圖6-16所示。

圖6-16 ArrayList類方法的使用
在本例中,定義了一個ArrayList類的對象list,list調用add()方法添加集合元素,通過它的iterator()方法獲取迭代器對象iterator,通過iterator對象和while循環輸出集合中的元素,可以看出集合中的元素就是按照add()方法的添加順序排列的。
list集合調用set(1)方法替換集合中指定索引1處的元素dog為mouse;調用get(2)方法獲取指定索引2處的元素,返回pig;調用indexOf("pig")方法獲取指定元素pig第一次出現的索引,即2;調用lastIndexOf("dog")方法獲取指定元素dog最后一次出現的索引,由于dog被mouse替換,不存在dog,所以返回-1;調用subList(1, 4)方法返回集合中從指定開始索引1到結束索引4間的一個新集合,不包含結束索引4處的元素。
注意:調用subList()方法返回的新集合中不包含結束索引處的元素。
2.LinkedList類
LinkedList實現了List接口,允許null元素。LinkedList類實現一個鏈表,可以對集合的首部和尾部進行插入和刪除操作,這些操作可以使LinkedList類被用作堆棧(stack)、隊列(queue)或雙向隊列(deque)。相對于ArrayList,LinkedList在插入或刪除元素時提供了更好的性能,但是隨機訪問元素的速度則相對較慢。LinkedList類除了繼承List接口的方法,又提供了一些方法,如表6-3所示。
表6-3 LinkedList類的方法

【例6-19】(實例文件:ch06\Chap6.19.txt)LinkedList類提供的方法的使用。
import java.util.Iterator; import java.util.LinkedList; public class LinkedListTest { public static void main(String[] args) { LinkedList list = new LinkedList(); //創建初始容量為10的空列表 list.add("cat"); list.add("dog"); list.add("pig"); list.add("sheep"); list.addLast("mouse"); list.addFirst("duck"); System.out.println("---輸出集合中的元素---"); Iterator iterator = list.iterator(); while(iterator.hasNext()){ System.out.print(iterator.next()+" "); } System.out.println(); System.out.println("獲取集合的第一個元素:" + list.getFirst()); System.out.println("獲取集合的最后一個元素:" + list.getLast()); System.out.println("刪除集合的第一個元素" + list.removeFirst()); System.out.println("刪除集合的最后一個元素" + list.removeLast()); System.out.println("---刪除元素后集合中的元素---"); iterator = list.iterator(); while(iterator.hasNext()){ System.out.print(iterator.next()+" "); } } }
程序運行結果如圖6-17所示。

圖6-17 LinkedList方法的使用
在本例中,定義一個鏈表集合list,通過實現類LinkedList自定義的addFirst()方法和addLast()方法,在鏈表的首部和尾部添加元素duck和mouse,可以看出添加元素的位置與這兩個方法的位置無關;但是在使用add()方法時,添加元素順序和add()方法順序有關。list調用getFirst()方法和getLast()方法獲取集合中第一個和最后一個元素,即duck和mouse;調用removeFirst()方法和removeLast()方法刪除集合中第一個和最后一個元素。
注意:LinkedList沒有同步方法。如果多個線程同時訪問一個List,則必須自己實現訪問同步。
6.5.4 Set接口

Set接口是Collection的子接口,Set接口沒有提供新增的方法。實現Set接口的容器類中的元素是沒有順序的,并且元素不可以重復。在Java API中提供的實現Set接口的容器類有HashSet、TreeSet等,它們是無序的容器類。
1.HashSet類
HashSet類實現了Set接口,不允許出現重復元素,不保證集合中元素的順序,允許包含值為null的元素,但最多只能有一個。HashSet添加一個元素時,會調用元素的hashCode()方法,獲得其哈希碼,根據這個哈希碼計算該元素在集合中的存儲位置。HashSet使用哈希算法存儲集合中的元素,可以提高集合元素的存儲速度。
HashSet類的常用構造方法有3種重載形式,具體如下:
(1)構造一個新的空Set集合。
public HashSet()
(2)構造一個包含指定集合中的元素的新Set集合。
public HashSet(Collection c)
(3)構造一個新的空Set集合,指定初始容量。
public HashSet(int initialCapacity)
【例6-20】(實例文件:ch06\Chap6.20.txt)HashSet類的使用。
import java.util.HashSet; import java.util.Iterator; public class HashSetTest { public static void main(String[] args) { HashSet hash = new HashSet(); hash.add("56"); hash.add("32"); hash.add("50"); hash.add("48"); hash.add("48"); hash.add("23"); System.out.println("集合元素個數:" + hash.size()); Iterator iter = hash.iterator(); while(iter.hasNext()){ System.out.print(iter.next() + " "); } } }
程序運行結果如圖6-18所示。

圖6-18 HashSet的使用
在本例中,定義了HashSet對象hash,通過調用它的add()方法添加集合元素,可以看到添加的重復元素48被覆蓋,Set集合中不允許存在重復元素。通過hash對象調用iterator()方法獲得迭代器,輸出集合中的元素,可以看到元素是無序的。
2.TreeSet類
TreeSet類不僅繼承了Set接口,還繼承了SortedSet接口,它不允許出現重復元素。由于SortedSet接口可以對集合中的元素進行自然排序(即升序排序),因此TreeSet類會對實現了Comparable接口的類的對象自動排序。TreeSet類提供的方法如表6-4所示。
表6-4 TreeSet類提供的方法

【例6-21】(實例文件:ch06\Chap6.21.txt)TreeSet類方法的使用。

程序運行結果如圖6-19所示。

圖6-19 TreeSet方法的使用
在本例中,定義了TreeSet對象tree,對象tree調用add()方法添加集合元素,這里添加的集合元素是String類的,由于String類實現了Comparable接口,所以TreeSet類對添加的元素進行了升序排序。
6.5.5 Map接口

Map接口是用來存儲“鍵-值”對的集合,存儲的“鍵-值”對是通過鍵來標識的,所以鍵不可以重復。Map接口的實現類有HashMap類和TreeMap類。
HashMap是基于哈希表的Map接口的實現類,以“鍵-值”對映射的形式存在。它在HashMap中總是被當作一個整體,系統會根據哈希算法來計算“鍵-值”對的存儲位置,具有很快的訪問速度,最多允許一條記錄的鍵為null,不支持線程同步。可以通過鍵快速地存取值。TreeMap類繼承了AbstractMap,可以對鍵對象進行排序。Map接口提供的方法如表6-5所示。
表6-5 Map接口提供的方法

【例6-22】(實例文件:ch06\Chap6.22.txt)HashMap類方法的使用。
import java.util.Iterator; import java.util.Set; import java.util.HashMap; public class HashMapTest { public static void main(String[] args) { HashMap map = new HashMap(); map.put("101", "一代天驕"); //添加“鍵-值”對 map.put("102", "成吉思汗"); //添加“鍵-值”對 map.put("103", "只識彎弓射大雕"); //添加“鍵-值”對 map.put("104", "俱往矣"); //添加“鍵-值”對 map.put("105", "數風流人物"); //添加“鍵-值”對 map.put("105", "還看今朝"); //添加“鍵-值”對 System.out.println("指定鍵102獲取值:" + map.get("102")); Set s = map.keySet(); //獲得HashMap鍵的集合 Iterator iterator = s.iterator(); //獲得HashMap中值的集合并輸出 String key = ""; while(iterator.hasNext()){ key = (String)iterator.next(); //獲得HashMap鍵的集合,強制轉換為String型 System.out.println(key + ":" + map.get(key)); } } }
程序運行結果如圖6-20所示。

圖6-20 HashMap類的使用
在本例中,定義了HashMap類的對象map,通過調用put()方法添加集合元素。map對象調用get("102")方法,獲得指定鍵的值“成吉思汗”。map調用iterator()方法獲得Iterator對象,通過iterator.next()方法獲得HashMap類中的鍵,并賦值給String型的key;map調用get(key)方法獲得HashMap類中的值,最后打印顯示鍵值對。
6.6 就業面試解析與技巧
6.6.1 面試解析與技巧(一)
面試官:抽象類和接口有什么異同?
應聘者:抽象類和接口都不能夠實例化,但可以定義抽象類和接口類型的引用。一個類如果繼承了某個抽象類或者實現了某個接口,都需要對其中的抽象方法全部進行實現,否則該類仍然需要被聲明為抽象類。接口比抽象類更加抽象,因為抽象類中可以定義構造器,可以有抽象方法和具體方法,而接口中不能定義構造方法,而且其中的方法都是抽象方法。
抽象類中的成員可以是private、默認、protected、public的,而接口中的成員全都是public的。抽象類中可以定義成員變量,而接口中定義的成員變量實際上都是常量。有抽象方法的類必須被聲明為抽象類,而抽象類未必要有抽象方法。
6.6.2 面試解析與技巧(二)
面試官:Comparable接口有什么作用?
應聘者:當需要排序的集合或數組不是單純的數字類型時,通常可以使用Comparable接口,以簡單的方式實現對象排序或自定義排序。這是因為Comparable接口內部有一個要重寫的關鍵的方法,即compareTo(T o),用于比較兩個對象的大小,這個方法返回一個整型數值。例如x.compareTo(y),情況如下:如果x和y相等,則方法返回值是0;如果x大于y,則方法返回值是大于0的值;如果x小于y,則方法返回小于0的值。因此,可以對實現了Comparable接口的類的對象進行排序。
- Hands-On Machine Learning with scikit:learn and Scientific Python Toolkits
- Learning Flask Framework
- C語言程序設計基礎與實驗指導
- Swift 3 New Features
- 游戲程序設計教程
- The DevOps 2.4 Toolkit
- Hands-On Enterprise Automation with Python.
- ADI DSP應用技術集錦
- HTML5與CSS3基礎教程(第8版)
- Spring核心技術和案例實戰
- Webpack實戰:入門、進階與調優
- Learning YARN
- 零基礎學Kotlin之Android項目開發實戰
- Visual C++從入門到精通(第2版)
- Java EE 程序設計