日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java集合类原理详解

發(fā)布時(shí)間:2023/12/3 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java集合类原理详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 1 集合框架
    • 1.1 集合框架概述
      • 1.1.1 容器簡(jiǎn)介
      • 1.1.1 容器的分類
    • 1.2 Collection
      • 1.2.1 常用方法
      • 1.2.2 迭代器
    • 1.3 List
      • 1.3.1 概述
      • 1.3.2 常用方法
      • 1.3.3 實(shí)現(xiàn)原理
    • 1.4 Map
      • 1.4.1 概述
      • 1.4.2 常用方法
      • 1.4.3 Comparable 接口
      • 1.4.4 實(shí)現(xiàn)原理
      • 1.4.5 覆寫 hashCode()
    • 1.5 Set
      • 1.5.1 概述
      • 1.5.2 常用方法
      • 1.5.3 實(shí)現(xiàn)原理
    • 1.6 總結(jié):集合框架中常用類比較
  • 2 練習(xí)
  • 3 附錄:排序

1 集合框架

1.1 集合框架概述

1.1.1 容器簡(jiǎn)介

到目前為止,我們已經(jīng)學(xué)習(xí)了如何創(chuàng)建多個(gè)不同的對(duì)象,定義了這些對(duì)象以后,我們就可以利用它們來(lái)做一些有意義的事情。

舉例來(lái)說(shuō),假設(shè)要存儲(chǔ)許多雇員,不同的雇員的區(qū)別僅在于雇員的身份證號(hào)。我們可以通過(guò)身份證號(hào)來(lái)順序存儲(chǔ)每個(gè)雇員,但是在內(nèi)存中實(shí)現(xiàn)呢?是不是要準(zhǔn)備足夠的內(nèi)存來(lái)存儲(chǔ) 1000個(gè)雇員,然后再將這些雇員逐一插入?如果已經(jīng)插入了 500條記錄,這時(shí)需要插入一 個(gè)身份證號(hào)較低的新雇員,該怎么辦呢?是在內(nèi)存中將 500 條記錄全部下移后,再?gòu)拈_頭插 入新的記錄? 還是創(chuàng)建一個(gè)映射來(lái)記住每個(gè)對(duì)象的位置?當(dāng)決定如何存儲(chǔ)對(duì)象的集合時(shí),必須考慮如下問(wèn)題。

對(duì)于對(duì)象集合,必須執(zhí)行的操作主要以下三種:
?添加新的對(duì)象
?刪除對(duì)象
?查找對(duì)象

我們必須確定如何將新的對(duì)象添加到集合中。可以將對(duì)象添加到集合的末尾、開頭或者中間的某個(gè)邏輯位置。

從集合中刪除一個(gè)對(duì)象后,對(duì)象集合中現(xiàn)有對(duì)象會(huì)有什么影響呢?可能必須將內(nèi)存移來(lái) 移去,或者就在現(xiàn)有對(duì)象所駐留的內(nèi)存位置下一個(gè)“洞”?

在內(nèi)存中建立對(duì)象集合后,必須確定如何定位特定對(duì)象。可建立一種機(jī)制,利用該機(jī)制 可根據(jù)某些搜索條件(例如身份證號(hào))直接定位到目標(biāo)對(duì)象;否則,便需要遍歷集合中的每 個(gè)對(duì)象,直到找到要查找的對(duì)象為止。

前面大家已經(jīng)學(xué)習(xí)過(guò)了數(shù)組。數(shù)組的作用是可以存取一組數(shù)據(jù)。但是它卻存在一些缺點(diǎn), 使得無(wú)法使用它來(lái)比較方便快捷的完成上述應(yīng)用場(chǎng)景的要求。

  • 首先,在很多數(shù)情況下面,我們需要能夠存儲(chǔ)一組數(shù)據(jù)的容器,這一點(diǎn)雖然數(shù)組可 以實(shí)現(xiàn),但是如果我們需要存儲(chǔ)的數(shù)據(jù)的個(gè)數(shù)多少并不確定。比如說(shuō),我們需要在 容器里面存儲(chǔ)某個(gè)應(yīng)用系統(tǒng)的當(dāng)前的所有的在線用戶信息,而當(dāng)前的在線用戶信息是時(shí)刻都可能在變化的。 也就是說(shuō),我們需要一種存儲(chǔ)數(shù)據(jù)的容器,它能夠自動(dòng)的改變這個(gè)容器的所能存放的數(shù)據(jù)數(shù)量的大小。這一點(diǎn)上,如果使用數(shù)組來(lái)存儲(chǔ)的話,就顯得十分的笨拙。

  • 我們?cè)偌僭O(shè)這樣一種場(chǎng)景:假定一個(gè)購(gòu)物網(wǎng)站,經(jīng)過(guò)一段時(shí)間的運(yùn)行,我們已經(jīng)存儲(chǔ)了一系列的購(gòu)物清單了,購(gòu)物清單中有商品信息。如果我們想要知道這段時(shí)間里 面有多少種商品被銷售出去了。那么我們就需要一個(gè)容器能夠自動(dòng)的過(guò)濾掉購(gòu)物清 單中的關(guān)于商品的重復(fù)信息。如果使用數(shù)組,這也是很難實(shí)現(xiàn)的。

  • 最后再想想,我們經(jīng)常會(huì)遇到這種情況,我知道某個(gè)人的帳號(hào)名稱,希望能夠進(jìn)一 步了解這個(gè)人的其他的一些信息。也就是說(shuō),我們?cè)谝粋€(gè)地方存放一些用戶信息, 我們希望能夠通過(guò)用戶的帳號(hào)來(lái)查找到對(duì)應(yīng)的該用戶的其他的一些信息。再舉個(gè)查字典例子:假設(shè)我們希望使用一個(gè)容器來(lái)存放單詞以及對(duì)于這個(gè)單詞的解釋,而當(dāng) 我們想要查找某個(gè)單詞的意思的時(shí)候,能夠根據(jù)提供的單詞在這個(gè)容器中找到對(duì)應(yīng)的單詞的解釋。如果使用數(shù)組來(lái)實(shí)現(xiàn)的話,就更加的困難了。

  • 為解決這些問(wèn)題,Java里面就設(shè)計(jì)了容器集合,不同的容器集合以不同的格式保存對(duì)象。

    數(shù)學(xué)背景

    在常見用法中,集合(collection)和數(shù)學(xué)上直觀的集(set)的概念是相同的。集是 一個(gè)唯一項(xiàng)組,也就是說(shuō)組中沒(méi)有重復(fù)項(xiàng)。實(shí)際上, “集合框架”包含了一個(gè) Set 接口和許 多具體的 Set 類。但正式的集概念卻比 Java 技術(shù)提前了一個(gè)世紀(jì),那時(shí)英國(guó)數(shù)學(xué)家 George Boole 按邏輯正式的定義了集的概念。大部分人在小學(xué)時(shí)通過(guò)我們熟悉的維恩圖 引入的“集的交”和“集的并”學(xué)到過(guò)一些集的理論。

    集的基本屬性如下:

    ?集內(nèi)只包含每項(xiàng)的一個(gè)實(shí)例
    ?集可以是有限的,也可以是無(wú)限的
    ?可以定義抽象概念

    集不僅是邏輯學(xué)、數(shù)學(xué)和計(jì)算機(jī)科學(xué)的基礎(chǔ),對(duì)于商業(yè)和系統(tǒng)的日常應(yīng)用來(lái)說(shuō),它也很實(shí)用。
    “連接池”這一概念就是數(shù)據(jù)庫(kù)服務(wù)器的一個(gè)開放連接集。 Web 服務(wù)器必須管理客戶機(jī)和連 接集。文件描述符提供了操作系統(tǒng)中另一個(gè)集的示例。

    映射是一種特別的集。它是一種對(duì)(pair)集,每個(gè)對(duì)表示一個(gè)元素到另一元素的單向映射。 一些映射示例有:
    ? IP地址到域名(DNS)的映射
    ? 關(guān)鍵字到數(shù)據(jù)庫(kù)記錄的映射
    ? 字典(詞到含義的映射)
    ? 2 進(jìn)制到 10 進(jìn)制轉(zhuǎn)換的映射

    就像集一樣,映射背后的思想比 Java 編程語(yǔ)言早的多,甚至比計(jì)算機(jī)科學(xué)還早。而 Java 中的 Map 就是映射的一種表現(xiàn)形式。

    1.1.1 容器的分類

    既然您已經(jīng)具備了一些集的理論,您應(yīng)該能夠更輕松的理解“集合框架”。 “集合框架” 由一組用來(lái)操作對(duì)象的接口組成。不同接口描述不同類型的組。在很大程度上,一旦您理解 了接口,您就理解了框架。雖然您總要?jiǎng)?chuàng)建接口特定的實(shí)現(xiàn),但訪問(wèn)實(shí)際集合的方法應(yīng)該限制在接口方法的使用上;因此,允許您更改基本的數(shù)據(jù)結(jié)構(gòu)而不必改變其它代碼。框架接口 層次結(jié)構(gòu)如下圖所示。

    Java 容器類類庫(kù)的用途是“保存對(duì)象”,并將其劃分為兩個(gè)不同的概念:
    1) Collection 。 一組對(duì)立的元素,通常這些元素都服從某種規(guī)則。List必須保持元素特定 的順序,而 Set 不能有重復(fù)元素。

    2) Map 。 一組 成對(duì)的“鍵值對(duì)”對(duì)象。初看起來(lái)這似乎應(yīng)該是一個(gè) Collection ,其元素 是成對(duì)的對(duì)象,但是這樣的設(shè)計(jì)實(shí)現(xiàn)起來(lái)太笨拙了,于是我們將Map明確的提取出來(lái)形 成一個(gè)獨(dú)立的概念。另一方面,如果使用Collection表示Map的部分內(nèi)容,會(huì)便于查看 此部分內(nèi)容。因此Map 一樣容易擴(kuò)展成多維Map,無(wú)需增加新的概念,只要讓Map 中的鍵值對(duì)的每個(gè)"值”也是一個(gè)Map即可。

    Collection 和 Map 的區(qū)別在于容器中每個(gè)位置保存的元素個(gè)數(shù)。 Collection 每個(gè)位置只能保 存一個(gè)元素(對(duì)象)。此類容器包括:List,它以特定的順序保存一組元素;Set則是元素 不能重復(fù)。

    Map保存的是“鍵值對(duì)”,就像一個(gè)小型數(shù)據(jù)庫(kù)。我們可以通過(guò)“鍵”找到該鍵對(duì)應(yīng)的“值”。

    ? Collection -對(duì)象之間沒(méi)有指定的順序,允許重復(fù)元素。
    ? Set - 對(duì)象之間沒(méi)有指定的順序,不允許重復(fù)元素
    ? List- 對(duì)象之間有指定的順序,允許重復(fù)元素,并引入位置下標(biāo)。
    ? Map - 接口用于保存關(guān)鍵字(Key)和數(shù)值(Value)的集合,集合中的每個(gè)對(duì)象 加入時(shí)都提供數(shù)值和關(guān)鍵字。 Map 接口既不繼承 Set 也不繼承 Collection。

    List、Set、Map共同的實(shí)現(xiàn)基礎(chǔ)是Object數(shù)組。

    除了四個(gè)歷史集合類外, Java2 框架還引入了六個(gè)集合實(shí)現(xiàn),如下表所示。


    這里沒(méi)有 Collection 接口的實(shí)現(xiàn),接下來(lái)我們?cè)賮?lái)看一下下面的這張關(guān)于集合框架的大 圖:

    這張圖看起來(lái)有點(diǎn)嚇人,熟悉之后就會(huì)發(fā)現(xiàn)其實(shí)只有三種容器:Map, List和Set,它 們各自有兩個(gè)三個(gè)實(shí)現(xiàn)版本。

    1.2 Collection

    1.2.1 常用方法

    Collection 接口用于表示任何對(duì)象或元素組。想要盡可能以常規(guī)方式處理一組元素時(shí), 就使用這一接口。Collection在前面的大圖也可以看出,它是List和Set的父類。并且它本 身也是一個(gè)接口。它定義了作為集合所應(yīng)該擁有的一些方法。如下:

    注意: 集合必須只有對(duì)象,集合中的元素不能是基本數(shù)據(jù)類型。

    Collection 接口支持如添加和除去等基本操作。設(shè)法除去一個(gè)元素時(shí),如果這個(gè)元素存在, 除去的僅僅是集合中此元素的一個(gè)實(shí)例。

    ? boolean add(Object element)
    ? boolean remove(Object element)

    Collection 接口還支持查詢操作:

    ? int size()
    ? boolean isEmpty()
    ? boolean contains(Object element)
    ? Iterator iterator()

    組操作 :Collection 接口支持的其它操作,要么是作用于元素組的任務(wù),要么是同時(shí)作用 于整個(gè)集合的任務(wù)。

    ? boolean containsAll(Collection collection)
    ? boolean addAll(Collection collection)
    ? void clear()
    ? void removeAll(Collection collection)
    ? void retainAll(Collection collection)

    containsAll() 方法允許您查找當(dāng)前集合是否包含了另一個(gè)集合的所有元素,即另一個(gè) 集合是否是當(dāng)前集合的子集。其余方法是可選的,因?yàn)樘囟ǖ募峡赡懿恢С旨细摹?addAll() 方法確保另一個(gè)集合中的所有元素都被添加到當(dāng)前的集合中,通常稱為并。 clear() 方法從當(dāng)前集合中除去所有元素。 removeAll() 方法類似于 clear() ,但 只除去了元素的一個(gè)子集。 retainAll() 方法類似于 removeAll() 方法,不過(guò)可能 感到它所做的與前面正好相反:它從當(dāng)前集合中除去不屬于另一個(gè)集合的元素,即交。

    我們看一個(gè)簡(jiǎn)單的例子,來(lái)了解一下集合類的基本方法的使用:

    import java.util.*; public class CollectionToArray { public static void main(String[] args) { Collection collection1 = new ArrayList();//倉(cāng)U建一個(gè)集合對(duì)象 collection1.add("000"); //添加對(duì)象到Collection 集合中 collection1.add("111"); collection1.add("222"); System.out.println("集合collection1的大小:"+collection1.size ()); System.out.println("集合collection1的內(nèi)容: "+collection1); collection1.remove ("000") ;//從集合collection1中移除掉"000"這個(gè) 對(duì)象 System.out.println("集合collection1移除 000 后的內(nèi)容:"+collection1); System.out.println("集合collection1中是否包含000 :"+collection1.contains("000")); System.out.println("集合collection中是否包含111:"+collection1.contains("111")); Collection collection2 = new ArrayList(); collection2.addAll(collection1);//將collection1集合中的元素全部添加到collection2 中 System.out.println("集合collection2的內(nèi)容:"+collection2); collection2.clear();//清空集合 collection1 中的元素 System.out.println("集合collection2是否為空 :"+collection2.isEmpty()); //將集合collection1轉(zhuǎn)化為數(shù)組 Object s[] = collection1.toArray(); for(int i=0;i<s.length;i++){ System.out.println(s[i]); } } }

    運(yùn)行結(jié)果為:
    集合collection1的大小:3
    集合 collection1 的內(nèi)容:[000, 111, 222]
    集合collection1移除000后的內(nèi)容:[111, 222]
    集合collection1中是否包含000 : false
    集合collection1中是否包含111 : true
    集合 collection2 的內(nèi)容:[111, 222]
    集合collection2是否為空:true
    111
    222

    這里需要注意的是, Collection 它僅僅只是一個(gè)接口,而我們真正使用的時(shí)候,確是創(chuàng)建該接口的一個(gè)實(shí)現(xiàn)類。做為集合的接口,它定義了所有屬于集合的類所都應(yīng)該具有的一些方法。

    而 ArrayList (列表)類是集合類的一種實(shí)現(xiàn)方式。

    這里需要一提的是,因?yàn)镃ollection的實(shí)現(xiàn)基礎(chǔ)是數(shù)組,所以有轉(zhuǎn)換為Object數(shù)組的方法:

    ? Object[] toArray()
    ? Object[] toArray(Object[] a)

    其中第二個(gè)方法Object[] toArray(Object[] a)的參數(shù)a 應(yīng)該是集合中所有存放的對(duì)象的類的父類。

    1.2.2 迭代器

    任何容器類,都必須有某種方式可以將東西放進(jìn)去,然后由某種方式將東西取出來(lái)。畢竟,存放事物是容器最基本的工作。對(duì)于ArrayList,add()是插入對(duì)象的方法,而get()是 取出元素的方式之一。ArrayList很靈活,可以隨時(shí)選取任意的元素,或使用不同的下標(biāo)一次選取多個(gè)元素。

    如果從更高層的角度思考,會(huì)發(fā)現(xiàn)這里有一個(gè)缺點(diǎn):要使用容器,必須知道其中元素的確切 類型。初看起來(lái)這沒(méi)有什么不好的,但是考慮如下情況:如果原本是ArrayList,但是后來(lái) 考慮到容器的特點(diǎn),你想換用 Set ,應(yīng)該怎么做?或者你打算寫通用的代碼,它們只是使用 容器,不知道或者說(shuō)不關(guān)心容器的類型,那么如何才能不重寫代碼就可以應(yīng)用于不同類型的容器?

    所以迭代器(Iterator)的概念,也是出于一種設(shè)計(jì)模式就是為達(dá)成此目的而形成的。所以Collection不提供get()方法。如果要遍歷Collectin中的元素就必須用Iterator。

    迭代器(Iterator)本身就是一個(gè)對(duì)象,它的工作就是遍歷并選擇集合序列中的對(duì)象,而 客戶端的程序員不必知道或關(guān)心該序列底層的結(jié)構(gòu)。此外,迭代器通常被稱為“輕量級(jí)”對(duì) 象,創(chuàng)建它的代價(jià)小。但是,它也有一些限制,例如,某些迭代器只能單向移動(dòng)。

    Collection 接口的 iterator() 方法返回一個(gè) Iterator。 Iterator 和您可 能已經(jīng)熟悉的 Enumeration 接口類似。使用 Iterator 接口方法,您可以從頭至尾遍 歷集合,并安全的從底層Collection中除去元素。

    下面,我們看一個(gè)對(duì)于迭代器的簡(jiǎn)單使用:

    import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class IteratorDemo { public static void main(String[] args) { Collection collection = new ArrayList(); collection.add("s1"); collection.add("s2"); collection.add("s3"); Iterator iterator = collection.iterator(); // 得到一個(gè)迭代器 while (iterator.hasNext()) { Object element = iterator.next(); System.out.println("iterator = " + element); } if (collection.isEmpty()) System.out.println("collection is Empty!"); else System.out.println("collection is not Empty! size="+collection.size()); Iterator iterator2 = collection.iterator(); while (iterator2. hasNext()) { // 移除元素 Object element = iterator2.next(); System.out.println("remove: " + element); iterator2.remove(); } Iterator iterator3 = collection.iterator(); if (!iterator3.hasNext()) {//察看是否還有元素 System.out.println("還有元素"); } if (collection.isEmpty()) System.out.println("collection is Empty!"); //使用 collection.isEmpty()方法來(lái)判斷 } }

    程序的運(yùn)行結(jié)果為:
    iterator = s1
    iterator = s2
    iterator = s3
    collection is not Empty! size=3
    remove: s1
    remove: s2
    remove: s3
    還有元素
    collection is Empty!

    可以看到, Java 的 Collection 的 Iterator 能夠用來(lái):

  • 使用方法 iterator() 要求容器返回一個(gè) Iterator ,第一次調(diào)用 Iterator 的 next() 方法 時(shí),它返回集合序列的第一個(gè)元素。
  • 使用 next() 獲得集合序列的中的下一個(gè)元素。
  • 使用hasNext()檢查序列中是否有元素。
  • 使用remove()將迭代器新返回的元素刪除。
  • 需要注意的是:方法刪除由next方法返回的最后一個(gè)元素,在每次調(diào)用next時(shí),remove方
    法只能被調(diào)用一次 。

    大家看,Java實(shí)現(xiàn)的這個(gè)迭代器的使用就是如此的簡(jiǎn)單。Iterator (迭代器)雖然功能簡(jiǎn) 單,但仍然可以幫助我們解決許多問(wèn)題,同時(shí)針對(duì)List還有一個(gè)更復(fù)雜更高級(jí)的ListIterator。 您可以在下面的List講解中得到進(jìn)一步的了解。

    1.3 List

    1.3.1 概述

    前面我們講述的Collection接口實(shí)際上并沒(méi)有直接的實(shí)現(xiàn)類。而List是容器的一種,表示列表的意思。當(dāng)我們不知道存儲(chǔ)的數(shù)據(jù)有多少的情況,我們就可以使用List來(lái)完成存儲(chǔ)數(shù)據(jù)的工作。例如前面提到的一種場(chǎng)景。我們想要在保存一個(gè)應(yīng)用系統(tǒng)當(dāng)前的在線 用戶的信息。我們就可以使用一個(gè)List來(lái)存儲(chǔ)。因?yàn)長(zhǎng)ist的最大的特點(diǎn)就是能夠自動(dòng) 的根據(jù)插入的數(shù)據(jù)量來(lái)動(dòng)態(tài)改變?nèi)萜鞯拇笮 O旅嫖覀兿瓤纯碙ist接口的一些常用方法。

    1.3.2 常用方法

    List 就是列表的意思,它是 Collection 的一種,即繼承了 Collection 接口,以定義 一個(gè)允許重復(fù)項(xiàng)的有序集合。該接口不但能夠?qū)α斜淼囊徊糠诌M(jìn)行處理,還添加了面向位置 的操作。 List 是按對(duì)象的進(jìn)入順序進(jìn)行保存對(duì)象,而不做排序或編輯操作。它除了擁有 Collection 接口的所有的方法外還擁有一些其他的方法。

    面向位置的操作包括插入某個(gè)元素或 Collection 的功能,還包括獲取、除去或更改元素的功能。在 List 中搜索元素可以從列表的頭部或尾部開始,如果找到元素,還將報(bào) 告元素所在的位置。

    ? void add(int index, Object element) :添加對(duì)象 element 到位置 index 上
    ? boolean addAll(int index, Collection collection) :在 index 位置后添加容器 collection 中所有的元素
    ? Object get(int index) :取出下標(biāo)為 index 的位置的元素
    ? int indexOf(Object element) :查找對(duì)象 element 在 List 中第一次出現(xiàn)的位置
    ? int lastIndexOf(Object element) :查找對(duì)象 element 在 List 中最后出現(xiàn)的位置
    ? Object remove(int index) :刪除 index 位置上的元素
    ? Object set(int index, Object element) :將 index 位置上的對(duì)象替換為 element 并返回 老的元素。

    先看一下下面表格:

    在“集合框架”中有兩種常規(guī)的 List 實(shí)現(xiàn): ArrayList 和 LinkedList 。使用兩種List 實(shí)現(xiàn)的哪一種取決于您特定的需要。如果要支持隨機(jī)訪問(wèn),而不必在除尾部的任何位 置插入或除去元素,那么, ArrayList 提供了可選的集合。但如果,您要頻繁的從列表的 中間位置添加和除去元素,而只要順序的訪問(wèn)列表元素,那么, LinkedList 實(shí)現(xiàn)更好。
    我們以ArrayList為例,先看一個(gè)簡(jiǎn)單的例子:

    public class ListDemo { pulilic static void main(String[] a) { String strMonths[] = { "1","2","3","4","5","6","7","8","9","10","11","12" }; // get count of months int nMonthLen = strMonths.length; // create arrayList object List months = new ArrayList(); // put months value to array list object for (int i = 0; i < nMonthLen; i++) { months.add(strMonths[i]); } // print all element in ArrayList for (int i = months.seze() - 1; i >= 0; i--) { System.out.println(months.get(i)); } } }

    例子中,我們把12個(gè)月份存放到ArrayList中,然后用一個(gè)循環(huán),并使用get ()方法將列表中的對(duì)象都取出來(lái)。

    而 LinkedList 添加了一些處理列表兩端元素的方法(下圖只顯示了新方法):

    使用這些新方法,您就可以輕松的把 LinkedList 當(dāng)作一個(gè)堆棧、隊(duì)列或其它面向端點(diǎn)的數(shù)據(jù)結(jié)構(gòu)。
    我們?cè)賮?lái)看另外一個(gè)使用LinkedList來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的隊(duì)列的例子:

    import java.util.*; public class ListExample { public static void main(String args[]) { LinkedList queue = new LinkedList(); queue.addFirst("Bernadine"); queue.addFirst("Elizabeth"); queue.addFirst("Gene"); queue.addFirst("Elizabeth"); queue.addFirst("Clara"); System.out.println(queue); queue.removeLast(); queue.removeLast(); System.out.println(queue); } }

    運(yùn)行程序產(chǎn)生了以下輸出。請(qǐng)注意,與 Set 不同的是 List 允許重復(fù)。

    [Clara, Elizabeth, Gene, Elizabeth, Bernadine]
    [Clara, Elizabeth, Gene]

    該程序演示了具體 List 類的使用。第一部分,創(chuàng)建一個(gè)由 ArrayList 支持的 List。 填充完列表以后,特定條目就得到了。示例的 LinkedList 部分把 LinkedList 當(dāng)作一 個(gè)隊(duì)列,從隊(duì)列頭部添加?xùn)|西,從尾部除去。

    List 接口不但以位置友好的方式遍歷整個(gè)列表,還能處理集合的子集:

    ? ListIterator listIterator():返回一個(gè) ListIterator 迭代器,默認(rèn)開始位置為 0
    ? ListIterator listIterator(int startIndex):返 回一個(gè) ListIterator 迭代器,開始位置為 startIndex
    ? List subList(int fromIndex, int toIndex) : 返回一個(gè)子列表 List ,元素存放為從 fromIndex 到 toIndex 之前的一個(gè)元素。

    處理 subList() 時(shí),位于 fromIndex 的元素在子列表中,而位于 toIndex 的元素則 不是,提醒這一點(diǎn)很重要。以下 for-loop 測(cè)試案例大致反映了這一點(diǎn):

    for (int i = fromIndex; i < toIndex; i++) { // process element at position i }

    此外,我們還應(yīng)該提醒的是:對(duì)子列表的更改(如 add()、remove() 和 set() 調(diào)用) 對(duì)底層 List 也有影響。

    ListIterator 接口

    ListIterator 接口繼承 Iterator 接口以支持添加或更改底層集合中的元素,還支持雙向訪問(wèn)。

    以下源代碼演示了列表中的反向循環(huán)。請(qǐng)注意 ListIterator 最初位于列表尾之后
    (list.size()),因?yàn)榈谝粋€(gè)元素的下標(biāo)是0。

    List list = ...; ListIterator iterator = list.listIterator(list.size()); while (iterator.hasPrevious()) { Object element = iterator.previous(); // Process element }

    正常情況下,不用 ListIterator 改變某次遍歷集合元素的方向 — 向前或者向后。雖然在技術(shù)上可能實(shí)現(xiàn)時(shí),但在 previous() 后立刻調(diào)用 next() ,返回的是同一個(gè)元素。 把調(diào)用 next() 和 previous() 的順序顛倒一下,結(jié)果相同。

    我們看一個(gè) List 的例子:

    import j ava.util.*; public class ListIteratorTest { public static void main(String[] args) { List list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); System.out.println ("下標(biāo)0開始: " + list.listIterator(0).next()); // next() System.out.println ("下標(biāo)1開始:" + list.listIterator(1).next()); System.out.println ("子List 1 -3 : " + list.subList(1,3)) ; // 子列表 Listiterator it = list.listIterator() ;//默認(rèn)從下標(biāo)0開始 //隱式光標(biāo)屬性add操作,插入到當(dāng)前的下標(biāo)的前面 it.add("sss"); while (it.hasNext()) { System.out.println("next Index = " + it.nextIndex() + ", Object = " + it.next()); } // set屬性 ListIterator it1 = list.listIterator(); it1.next(); it1.set("ooo"); ListIterator it2 = list.listIterator(list.size());// 下標(biāo) while (it2 .hasPrevious()) { System.out.println("previous Index = " + it2.previousIndex() + ", Object = " + it2.previous()); } } }

    程序的執(zhí)行結(jié)果為:
    下標(biāo)0開始:aaa
    下標(biāo)1開始:bbb
    子List 1-3:[bbb, ccc]
    next Index = 1, Object = aaa
    next Index = 2, Object = bbb
    next Index = 3, Object = ccc
    next Index = 4, Object = ddd
    previous Index = 4, Object = ddd
    previous Index = 3,Object = ccc
    previous Index = 2, Object = bbb
    previous Index = 1, Object = aaa
    previous Index = 0, Object = ooo

    我們還需要稍微再解釋一下 add() 操作。添加一個(gè)元素會(huì)導(dǎo)致新元素立刻 被添加到隱式光標(biāo)的前面。因此,添加元素后調(diào)用 previous() 會(huì)返回新元素, 而調(diào)用 next() 則不起作用,返回添加操作之前的下一個(gè)元素。下標(biāo)的顯示方式,如下圖所示:

    對(duì)于List的基本用法我們學(xué)會(huì)了,下面我們來(lái)進(jìn)一步了解一下List的實(shí)現(xiàn)原理,以便加深我們對(duì)于集合的理解。

    1.3.3 實(shí)現(xiàn)原理

    前面己經(jīng)提了一下Collection的實(shí)現(xiàn)基礎(chǔ)都是基于數(shù)組的。下面我們就已ArrayList為例,簡(jiǎn)單分析一下 ArrayList 列表的實(shí)現(xiàn)方式。首先,先看下它的構(gòu)造函數(shù)。
    下列表格是在SUN提供的API中的描述:

    其中第一個(gè)構(gòu)造函數(shù)ArrayList ()和第二構(gòu)造函數(shù)ArrayList (Collection c)是按照 Collection 接口文檔所述,所應(yīng)該提供兩個(gè)構(gòu)造函數(shù),一個(gè)無(wú)參數(shù),一個(gè)接受另一個(gè) Collection。

    第 3 個(gè)構(gòu)造函數(shù):
    ArrayList(int initialCapacity) 是 ArrayList 實(shí)現(xiàn)的比較重要的構(gòu)造函數(shù),雖然, 我們不常用它,但是默認(rèn)的構(gòu)造函數(shù)正是調(diào)用帶參數(shù): initialCapacity 的構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)的。其中參數(shù):initialCapacity表示我們構(gòu)造的這個(gè)ArrayList列表的初始化容量是多大。如果調(diào)用默認(rèn)的構(gòu)造函數(shù),則表示默認(rèn)調(diào)用該參數(shù)為initialCapacity =10 的方式,來(lái)進(jìn)行構(gòu)建一個(gè) ArrayList 列表對(duì)象。

    為了更好的理解這個(gè)initialCapacity參數(shù)的概念,我們先看看ArrayList在Sun提 供的源碼中的實(shí)現(xiàn)方式。先看一下它的屬性有哪些:

    public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * The array buffer into which the elements of the ArrayList are stored? * The capac ity of the ArrayL ist is the length of this array buffer. * 列表的實(shí)現(xiàn)核心屬性:數(shù)組。我們使用診數(shù)組未進(jìn)行存放集合中的數(shù)據(jù)。 * 而我們的初始化參數(shù)就是該數(shù)組構(gòu)建時(shí)候的長(zhǎng)度,即訪數(shù)組的性就是initialCapacity */ private transient Object elementData[]; /** * The size of the ArrayList (the number of elements it contains). * 列表中真實(shí)數(shù)據(jù)的存放個(gè)數(shù) * @serial */ private int size;

    ArrayList 繼承了AbstractList 我們主要看看 ArrayList 中的屬性就可以了。
    ArrayList中主要包含 2 個(gè)屬性:

    ? private transient Object elementData[];
    ? private int size;

    其中數(shù)組:: elementData[] 是列表的實(shí)現(xiàn)核心屬性:數(shù)組。 我們使用該數(shù)組來(lái)進(jìn)行存 放集合中的數(shù)據(jù)。而我們的初始化參數(shù)就是該數(shù)組構(gòu)建時(shí)候的長(zhǎng)度,即該數(shù)組的 length 屬性就是 initialCapacity 參數(shù)。

    Keys: transient表示被修飾的屬性不是對(duì)象持久狀態(tài)的一部分,不會(huì)自動(dòng)的序列化。

    第2個(gè)屬性:size表示列表中真實(shí)數(shù)據(jù)的存放個(gè)數(shù)。

    我們?cè)賮?lái)看一下ArrayList的構(gòu)造函數(shù),加深一下ArrayList是基于數(shù)組的理解。

    public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IIlegalArgumentException("IIlegal Capacity:" + initialCapacity); // 構(gòu)建一個(gè)初始化長(zhǎng)度為initialcapacity的數(shù)組對(duì)象 this.elementData = new Object[initialcapacity]; /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this(10); }

    從源碼中可以看到默認(rèn)的構(gòu)造函數(shù)調(diào)用的就是帶參數(shù)的構(gòu)造函數(shù):
    public ArrayList(int initialCapacity)
    不過(guò)參數(shù) initialCapacity = 10。
    我們主要看 ArrayList(int initialCapacity) 這個(gè)構(gòu)造函數(shù)。可以看到: this.elementData = new Object[initialCapacity];
    我們就是使用的 initialCapacity 這個(gè)參數(shù)來(lái)創(chuàng)建一個(gè) Object 數(shù)組。而我們所有的往該集合對(duì)象中存放的數(shù)據(jù),就是存放到了這個(gè)Object數(shù)組中去了。

    我們?cè)诳纯戳硗庖粋€(gè)構(gòu)造函數(shù)的源碼:

    * 通過(guò)另外一個(gè)容器對(duì)象要構(gòu)建一個(gè)List,構(gòu)建的數(shù)組初始化長(zhǎng)度為另外一個(gè)容器的size屬性的1.1* @param c the collection whose elements are to be placed into this list. * @throws NullPointerException if the specified collection is null. */ public ArrayList(Collection c) { size = c.size(); // 當(dāng)前元素的個(gè)數(shù)為另外一個(gè)容器中的元素的個(gè)數(shù) // Allow 10% room for growth (擴(kuò)充1.1倍的容量) elementData = new Object[(int) Math.min((size * 110L) / 100, Integer.MAX_VALUE)]; c.toArray(elementData); } /** * Returns the number of elements in this list. * 返回List中元素的個(gè)數(shù) * @returnthe number of elements in this list. */ public int size () { return size; }

    這里,我們先看size()方法的實(shí)現(xiàn)形式。它的作用即是返回size屬性值的大小。 然后我們?cè)倏戳硗庖粋€(gè)構(gòu)造函數(shù) public ArrayList(Collection c) ,該構(gòu)造函數(shù) 的作用是把另外一個(gè)容器對(duì)象中的元素存放到當(dāng)前的List對(duì)象中。

    可以看到,首先,我們是通過(guò)調(diào)用另外一個(gè)容器對(duì)象C的方法size()來(lái)設(shè)置當(dāng)前的 List 對(duì)象的 size 屬性的長(zhǎng)度大小。

    接下來(lái),就是對(duì)elementData數(shù)組進(jìn)行初始化,初始化的大小為原先容器大小的 1.1倍。最后,就是通過(guò)使用容器接口中的 Object[] toArray(Object[] a) 方法來(lái)把當(dāng)前容器 中的對(duì)象都存放到新的數(shù)組elementData中。這樣就完成了一個(gè)ArrayList的建立。

    可能大家會(huì)存在一個(gè)問(wèn)題,那就是,我們建立的這個(gè)ArrayList是使用數(shù)組來(lái)實(shí)現(xiàn) 的,但是數(shù)組的長(zhǎng)度一旦被定下來(lái),就不能改變了。而我們?cè)诮oArrayList對(duì)象中添加元素的時(shí)候,卻沒(méi)有長(zhǎng)度限制。這個(gè)時(shí)候, ArrayList 中的 elementData 屬性就必須 存在一個(gè)需要?jiǎng)討B(tài)的擴(kuò)充容量的機(jī)制。我們看下面的代碼,它描述了這個(gè)擴(kuò)充機(jī)制:

    * 該方法用來(lái)判斷當(dāng)前的數(shù)組是否需要擴(kuò)容,應(yīng)該擴(kuò)容多少 * @param minCapacity the desired minimum capacity */public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { // 如果minCapacity大于老的elementData數(shù)組的長(zhǎng)度,那么就需要擴(kuò)容 Object oldData[] = elementData; // 新的數(shù)組的長(zhǎng)度為原來(lái)長(zhǎng)度1.5倍加1,或者為minCapacity int newCapacity = (oldCapacity * 3) / 2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; elementData = new Object[newCapacity]; System.arraycopy(oldData, 0, elementData, 0, size); } }

    這個(gè)方法的作用就是用來(lái)判斷當(dāng)前的數(shù)組是否需要擴(kuò)容,應(yīng)該擴(kuò)容多少。其中屬性: modCount是繼承自父類,它表示當(dāng)前的對(duì)象對(duì)elementData數(shù)組進(jìn)行了多少次擴(kuò)容, 清空,移除等操作。該屬性相當(dāng)于是一個(gè)對(duì)于當(dāng)前List對(duì)象的一個(gè)操作記錄日志號(hào)。我們主要看下面的代碼實(shí)現(xiàn):

  • 首先得到當(dāng)前 elementData 屬性的長(zhǎng)度 oldCapacity。
  • 然后通過(guò)判斷 oldCapacity 和 minCapacity 參數(shù)誰(shuí)大來(lái)決定是否需要擴(kuò)容
    • 如果minCapacity大于oldCapacity,那么我們就對(duì)當(dāng)前的List對(duì)象進(jìn) 行擴(kuò)容。擴(kuò)容的的策略為:取 (oldCapacity * 3)/2 + 1和minCapacity 之間更大的那個(gè)。然后使用數(shù)組拷貝的方法,把以前存放的數(shù)據(jù)轉(zhuǎn)移到新的數(shù) 組對(duì)象中
    • 如果 minCapacity 不大于 oldCapacity 那么就不進(jìn)行擴(kuò)容

    下面我們看看ensureCapacity方法是如何使用的:

    * Appends the specified element to the end of this list. * @param o element to be appended to this list. * @return <tt>true</tt> (如果添加成功就返回true). */ public boolean add(Object o) { / /調(diào)用ensureCapacity方法來(lái)確定是否需要擴(kuò)容 ensureCapacity (size + 1) ; // Increments modCount!! elementData[size++] = o; return true; }* 該方法用來(lái)將另外一個(gè)容器C中的元素都添加到當(dāng)前的List* @param c the elements to be inserted into this list. * @return <tt>true</tt> 如果添加的元素個(gè)數(shù)不為0,就返回true * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection c) { Object[] a = c.toArray(); int numNew = a.length; // 判斷是否需要擴(kuò)容 ensureCapacity (size + numNew) ; // Increments modCount!! System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }

    上的兩個(gè)a d d方法都是往List中添加元素。每次在添加元素的時(shí)候,我們就需要判斷 一下,是否需要對(duì)于當(dāng)前的數(shù)組進(jìn)行擴(kuò)容。

    我們主要看看 public boolean add (Object o)方法,可以發(fā)現(xiàn)在添加一個(gè)元素到容 器中的時(shí)候,首先我們會(huì)判斷是否需要擴(kuò)容。因?yàn)橹辉黾右粋€(gè)元素,所以擴(kuò)容的大小判斷也 就為當(dāng)前的size + 1來(lái)進(jìn)行判斷。然后,就把新添加的元素放到數(shù)組elementData中。

    第二個(gè)方法 public boolean addAll(Collection c) 也是同樣的原理。將新的元素放到 elementData 數(shù)組之后。同時(shí)改變當(dāng)前 List 對(duì)象的 size 屬性。

    類似的 List 中的其他的方法也都是基于數(shù)組進(jìn)行操作的。大家有興趣可以看看源碼中的更多的實(shí)現(xiàn)方式。
    最后我們?cè)倏纯慈绾闻袛嘣诩现惺欠褚呀?jīng)存在某一個(gè)對(duì)象的:

    /* * 判斷在當(dāng)前的List是否包含某個(gè)對(duì)象 * @param elem element whose presence in this List is to be tested * @return 存在返回<code>true</code>;否則返回false */ public boolean contains (Object elem) { // 調(diào)用indexOf方法,如果返回值大于-1則表示存在該elem對(duì)象 return indexOf(elem) >= 0; } /* * 將是包含某于對(duì)象elem的位置返回,如果不包含該對(duì)象則返回-1 * @param elem an object * @return 返回該對(duì)象第一次出現(xiàn)的位置,如果環(huán)存在則返回-1 * @see Object#equaIs(Object)[通過(guò)對(duì)象的equals來(lái)判斷是否相等] */ public int inclexOf(Object elem) { if (elem == null) { for (int i = 0; i < size; i++) if (elementData[i] == null) return i; } else { // 使用elem象的equals來(lái)判斷是否在數(shù)組中存在該對(duì)象 for (int i = 0; i < size; i++) if (elem.equals(elementData[i])) return i; } return -1; }

    由源碼中我們可以看到, public boolean contains(Object elem) 方法是通過(guò)調(diào)用 public int indexOf(Object elem) 方法來(lái)判斷是否在集合中存在某個(gè)對(duì)象elem。我們看看indexOf方法的具體實(shí)現(xiàn)。

    ? 首先我們判斷一下elem對(duì)象是否為null ,如果為null的話,那么遍歷數(shù)組 elementData 把第一個(gè)出現(xiàn) null的位置返回。
    ? 如果 elem 不為 null 的話,我們也是遍歷數(shù)組 elementData ,并通過(guò)調(diào)用 elem對(duì)象的equals()方法來(lái)得到第一個(gè)相等的元素的位置。
    這里我們可以發(fā)現(xiàn),ArrayList中用來(lái)判斷是否包含一個(gè)對(duì)象,調(diào)用的是各個(gè)對(duì)象自己實(shí)現(xiàn)的 equals() 方法。在前面的高級(jí)特性里面,我們可以知道:如果要判斷一個(gè)類的一 個(gè)實(shí)例對(duì)象是否等于另外一個(gè)對(duì)象, 那么我們就需要自己覆寫 Object 類的 public boolean equals(Object obj) 方法。如果不覆寫該方法的話,那么就會(huì)調(diào)用 Object 的 equals() 方法來(lái)進(jìn)行判斷。這就相當(dāng)于比較兩個(gè)對(duì)象的內(nèi)存應(yīng)用地址是否相等了。
    在集合框架中,不僅僅是List,所有的集合類,如果需要判斷里面是否存放了的某個(gè)對(duì)象,都是調(diào)用該對(duì)象的equals()方法來(lái)進(jìn)行處理的。

    1.4 Map

    1.4.1 概述

    數(shù)學(xué)中的映射關(guān)系在Java中就是通過(guò)Map來(lái)實(shí)現(xiàn)的。它表示,里面存儲(chǔ)的元素是一個(gè)對(duì) (pair) ,我們通過(guò)一個(gè)對(duì)象,可以在這個(gè)映射關(guān)系中找到另外一個(gè)和這個(gè)對(duì)象相關(guān)的東西。 前面提到的我們對(duì)于根據(jù)帳號(hào)名得到對(duì)應(yīng)的人員的信息,就屬于這種情況的應(yīng)用。我們講一 個(gè)人員的帳戶名和這人員的信息作了一個(gè)映射關(guān)系,也就是說(shuō),我們把帳戶名和人員信息當(dāng) 成了一個(gè)“鍵值對(duì)”,“鍵”就是帳戶名,“值”就是人員信息。下面我們先看看Map接口的常用方法。

    1.4.2 常用方法

    Map 接口不是 Collection 接口的繼承。而是從自己的用于維護(hù)鍵-值關(guān)聯(lián)的接口層次結(jié)
    構(gòu)入手。按定義,該接口描述了從不重復(fù)的鍵到值的映射。

    我們可以把這個(gè)接口方法分成三組操作:改變、查詢和提供可選視圖。

    改變操作允許您從映射中添加和除去鍵-值對(duì)。鍵和值都可以為null。但是,您不能把Map 作為一個(gè)鍵或值添加給自身。

    ? Object put(Object key,Object value):用來(lái)存放一個(gè)鍵-值對(duì) Map 中
    ? Object remove(Object key):根據(jù)key (鍵),移除一個(gè)鍵-值對(duì),并將值返回
    ? void putAll(Map mapping) :將另外一個(gè) Map 中的元素存入當(dāng)前的 Map 中
    ? void clear():清空當(dāng)前 Map 中的元素

    查詢操作允許您檢查映射內(nèi)容:

    ? Object get (Object key):根據(jù)key (鍵)取得對(duì)應(yīng)的值
    ? boolean containsKey (Object key):判斷 Map 中是否存在某鍵(key)
    ? boolean containsValue(Object value):判斷 Map 中是否存在某值 (value)
    ? int size ():返回Map中鍵-值對(duì)的個(gè)數(shù)
    ? boolean isEmpty():判斷當(dāng)前 Map 是否為空

    最后一組方法允許您把鍵或值的組作為集合來(lái)處理:

    ? public Set keySet ():返回所有的鍵(key),并使用Set容器存放
    ? public Collection values ():返回所有的值(Value),并使用 Collection 存放
    ? public Set entrySet() :返回一個(gè)實(shí)現(xiàn) Map.Entry 接口的元素 Set, 因?yàn)橛成渲墟I的集合必須是唯一的,就使用 Set 來(lái)支持。因?yàn)橛成渲兄档募峡赡懿晃ㄒ?#xff0c; 就使用 Collection 來(lái)支持。最后一個(gè)方法返回一個(gè)實(shí)現(xiàn) Map.Entry 接口的元素 Set。
    我們看看 Map 的常用實(shí)現(xiàn)類的比較,如下表:

    下面我們看一個(gè)簡(jiǎn)單的例子:

    import java.util.*; public class MapTest { public static void main(String[] args) { Map map1 = new HashMap(); Map map2 = new HashMap(); map1.put("1","aaa1"); map1.put("2","bbb2"); map2.put("10","aaaa10"); map2.put("11","bbbb11"); // 根據(jù)鍵"1"取得值:"aaa1" System.out.println("map1.get(\"1\")="+map1.get("1")); //根據(jù)鍵"1"移除鍵值對(duì)"1"-"aaa1" System.out.println("map1.remove (\"1\") =" + map1.remove ("1")); System.out.println("map1.get(\"1\")=" + map1.get("1")); map1.putAll(map2); //將map2全部元素放入map1中 map2.clear(); // 清空map2 System.out.println("map1 IsEmpty?=" + map1.isEmpty()); System.out.println("map2 IsEmpty?=" + map2.isEmpty()); System.out.println("map1中的鍵值對(duì)的個(gè)數(shù)size = " + map1.size()); System.out.println("KeySet=" + map1.keySet()); // set System.out.println("values=" + map1.values()); // Collection System.out.println("entrySet=" + map1.entrySet()); System.out.println("map1是否包含鍵:11 = " + map1.containsKey("11")); System.out.println ( "map1是否包含值:aaa1 = " + map1.containsValue("aaa1")); } } 運(yùn)行輸出結(jié)果為: map1.get("1")=aaa1 map1.remove("1")=aaa1 map1.get("1")=null map1 IsEmpty?=false map2 IsEmpty?=true map1中的鍵值對(duì)的個(gè)數(shù)size = 3 KeySet=[10, 2, 11] values=[aaaa10, bbb2, bbbb11] entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11] map1 是否包含鍵:11 = true map1 是否包含值: aaa1 = false

    在該例子中,我們創(chuàng)建一個(gè)HashMap,并使用了一下Map接口中的各個(gè)方法。
    其中 Map 中的 entrySet() 方法先提一下,該方法返回一個(gè)實(shí)現(xiàn) Map.Entry 接口的對(duì)象集合。集合中每個(gè)對(duì)象都是底層 Map 中一個(gè)特定的鍵-值對(duì)。

    Map.Entry接口是Map接口中的一個(gè)內(nèi)部接口,該內(nèi)部接口的實(shí)現(xiàn)類存放的是鍵值對(duì)。 在下面的實(shí)現(xiàn)原理中,我們會(huì)對(duì)這方面再作介紹,現(xiàn)在我們先不管它的具體實(shí)現(xiàn)。 我們?cè)倏纯磁判虻?Map 是如何使用:

    import java.util.*; public class MapSortExample { public static void main(String args[]) { Map map1 = new HashMap(); Map map2 = new LinkedHashMap(); for (int i = 0; i < 10; i++) { double s = Math.random() * 100; // 產(chǎn)生一個(gè)隨機(jī)數(shù),并將其放入Map中 map1.put(new Integer((int)s), "第" + i + "個(gè)放入的元素:" + s + "\n"); map2.put(new Integer((int)s), "第" + i + "個(gè)放入的元素:" + s + "\n"); } System.out.println("未排序前HashMap:" + map1); System.out.println("未排序前LinkedHashMap: " + map2); // 使用TreeMap來(lái)對(duì)另外的Map進(jìn)行重構(gòu)和排序 Map sortedMap = new TreeMap(map1); System.out.println("排序后:" + sortedMap); System.out.println("排序后:" + new TreeMap (map2)); } } 該程序的一次運(yùn)行結(jié)果為: 未排序前HashMap:{ 64=第 1個(gè)放入的元素:64.05341725531845, 15=第 9 個(gè)放入的元素:15.249165766266382, 2=第 4 個(gè)放入的元素:2.66794706854534, 77=第 0 個(gè)放入的元素:77.28814965781416, 97=第 5 個(gè)放入的元素:97.32893518378948, 99=第 2 個(gè)放入的元素:99.99412014935982, 60=第 8 個(gè)放入的元素:60.91451419025399, 6=第 3 個(gè)放入的元素:6.286974058646977, 1=第 7 個(gè)放入的元素:1.8261658496439903, 48=第 6 個(gè)放入的元素:48.736039522423106 } 未排序前LinkedHashMap: { 77=第 0 個(gè)放入的元素:77.28814965781416, 64=第 1 個(gè)放入的元素:64.05341725531845, 99=第 2 個(gè)放入的元素:99.99412014935982, 6=第 3 個(gè)放入的元素:6.286974058646977, 2=第 4 個(gè)放入的元素:2.66794706854534, 97=第 5 個(gè)放入的元素:97.32893518378948, 48=第 6 個(gè)放入的元素:48.736039522423106, 1=第 7 個(gè)放入的元素:1.8261658496439903, 60=第 8 個(gè)放入的元素:60.91451419025399, 15=第 9 個(gè)放入的元素:15.249165766266382 } 排序后:{ 1=第7個(gè)放入的元素:1.8261658496439903, 2=第 4 個(gè)放入的元素:2.66794706854534, 6=第 3 個(gè)放入的元素:6.286974058646977, 15=第 9 個(gè)放入的元素:15.249165766266382, 48=第 6 個(gè)放入的元素:48.736039522423106, 60=第 8 個(gè)放入的元素:60.91451419025399, 64=第 1 個(gè)放入的元素:64.05341725531845, 77=第 0 個(gè)放入的元素:77.28814965781416, 97=第 5 個(gè)放入的元素:97.32893518378948, 99=第 2 個(gè)放入的元素:99.99412014935982 } 排序后:{ 1=第7個(gè)放入的元素:1.8261658496439903, 2=第 4 個(gè)放入的元素:2.66794706854534, 6=第 3 個(gè)放入的元素:6.286974058646977, 15=第 9 個(gè)放入的元素:15.249165766266382, 48=第 6 個(gè)放入的元素:48.736039522423106, 60=第 8 個(gè)放入的元素:60.91451419025399, 64=第 1 個(gè)放入的元素:64.05341725531845, 77=第 0 個(gè)放入的元素:77.28814965781416, 97=第 5 個(gè)放入的元素:97.32893518378948, 99=第 2 個(gè)放入的元素:99.99412014935982 }

    從運(yùn)行結(jié)果,我們可以看出,HashMap的存入順序和輸出順序無(wú)關(guān)。而LinkedHashMap 則保留了鍵值對(duì)的存入順序。TreeMap則是對(duì)Map中的元素進(jìn)行排序。在實(shí)際的使用中我 們也經(jīng)常這樣做:使用HashMap或者LinkedHashMap來(lái)存放元素,當(dāng)所有的元素都存放完成后,如果使用是需要一個(gè)經(jīng)過(guò)排序的 Map 的話,我們?cè)偈褂?TreeMap 來(lái)重構(gòu)原來(lái)的 Map對(duì)象。這樣做的好處是:因?yàn)镠ashMap和LinkedHashMap存儲(chǔ)數(shù)據(jù)的速度比直接使 用TreeMap 要快,存取效率要高。當(dāng)完成了所有的元素的存放后,我們?cè)賹?duì)整個(gè)的 Map中的元素進(jìn)行排序。這樣可以提高整個(gè)程序的運(yùn)行的效率,縮短執(zhí)行時(shí)間。
    這里需要注意的是,TreeMap中是根據(jù)鍵(Key)進(jìn)行排序的。而如果我們要使用TreeMap 來(lái)進(jìn)行正常的排序的話,Key中存放的對(duì)象必須實(shí)現(xiàn)Comparable接口。

    1.4.3 Comparable 接口

    在 java.lang 包中, Comparable 接口適用于一個(gè)類有自然順序的時(shí)候。假定對(duì)象集合 是同一類型,該接口允許您把集合排序成自然順序。

    它只有一個(gè)方法: compareTo() 方法,用來(lái)比較當(dāng)前實(shí)例和作為參數(shù)傳入的元素。 如果排序過(guò)程中當(dāng)前實(shí)例出現(xiàn)在參數(shù)前(當(dāng)前實(shí)例比參數(shù)大),就返回某個(gè)負(fù)值。如果當(dāng)前 實(shí)例出現(xiàn)在參數(shù)后(當(dāng)前實(shí)例比參數(shù)小),則返回正值。否則,返回零。如果這里不要求零返回值表示元素相等。零返回值可以只是表示兩個(gè)對(duì)象在排序的時(shí)候排在同一個(gè)位置。

    上面例子中的整形的包裝類: Integer 就實(shí)現(xiàn)了該接口。我們可以看一下這個(gè)類的源碼:

    public final class Integer extends Number implements Comparable { public int compareTo(Object o) { return compareTo((Integer)o); }public int compareTo(Integer anotherInteger) { int thisVal = this.value; int anotherVal = anotherInteger.value; return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); }

    可以看到 compareTo 方法里面通過(guò)判斷當(dāng)前的 Integer 對(duì)象的值是否大于傳入的參數(shù)的值來(lái)得到返回值的。

    在Java 2 SDK,版本1.2中有十四個(gè)類實(shí)現(xiàn)Comparable接口。下表展示了它們的自 然排序。雖然一些類共享同一種自然排序,但只有相互可比的類才能排序。


    這里只是簡(jiǎn)單的介紹一下排序接口,如果要詳細(xì)的了解排序部分內(nèi)容的話,可以參考文章最 后的附錄部分對(duì)于排序的更加詳細(xì)的描述。

    我們?cè)倩氐組ap中來(lái),Java提高的API中除了上面介紹的幾種Map比較常用以為還有一些Map,大家可以了解一下:
    ? WeakHashMap: WeakHashMap 是 Map 的一個(gè)特殊實(shí)現(xiàn),它只用于存儲(chǔ)對(duì)鍵的弱引用。當(dāng)映射的某個(gè)鍵在 WeakHashMap 的外部不再被引用時(shí),就允許垃圾收集器收集映射中相應(yīng)的鍵值對(duì)。使用 WeakHashMap 有益于保持類似注冊(cè)表的數(shù)據(jù)結(jié)構(gòu),其中條目的鍵不再被任何線程訪問(wèn)時(shí),此條目就沒(méi)用了。
    ? IdentifyHashMap : Map 的一種特性實(shí)現(xiàn), 關(guān)鍵屬性的 hash 碼不是由hashCode() 方法計(jì)算,而是由 System.identityHashCode 方法計(jì)算,使用 == 進(jìn)行比較而不是 equals() 方法。

    通過(guò)簡(jiǎn)單的對(duì)Map中各個(gè)常用實(shí)現(xiàn)類的使用,為了更好的理解Map,下面我們?cè)賮?lái) 了解一下 Map 的實(shí)現(xiàn)原理。

    1.4.4 實(shí)現(xiàn)原理

    有的人可能會(huì)認(rèn)為Map會(huì)繼承Collection。在數(shù)學(xué)中,映射只是對(duì)(pair)的集合。但是,在“集合框架”中,接口 Map 和 Collection 在層次結(jié)構(gòu)沒(méi)有任何親緣關(guān)系,它們是截然不同的。這種差別的原因與 Set 和 Map 在 Java 庫(kù)中使用的方法有關(guān)。 Map 的典型應(yīng)用是訪問(wèn)按關(guān)鍵字存儲(chǔ)的值。它支持一系列集合操作的全部,但操作的是鍵-值對(duì), 而不是單個(gè)獨(dú)立的元素。因此 Map 需要支持 get() 和 put() 的基本操作,而 Set 不 需要。此外,還有返回 Map 對(duì)象的 Set 視圖的方法:

    Set set = aMap.keySet();

    下面我們以HashMap為例,對(duì)Map的實(shí)現(xiàn)機(jī)制作一下更加深入一點(diǎn)的理解。

    因?yàn)镠ashMap里面使用Hash算法,所以在理解HashMap之前,我們需要先了解一下Hash 算法和 Hash 表。

    Hash,一般翻譯做“散列”,也有直接音譯為"哈希"的,就是把任意長(zhǎng)度的輸入(又叫做 預(yù)映射,pre-image),通過(guò)散列算法,變換成固定長(zhǎng)度的輸出,該輸出就是散列值。這種轉(zhuǎn)換是一種壓縮映射,也就是,散列值的空間通常遠(yuǎn)小于輸入的空間,不同的輸入可能會(huì)散列成相同的輸出,而不可能從散列值來(lái)唯一的確定輸入值。

    說(shuō)的通俗一點(diǎn),Hash算法的意義在于提供了一種快速存取數(shù)據(jù)的方法,它用一種算法建 立鍵值與真實(shí)值之間的對(duì)應(yīng)關(guān)系,(每一個(gè)真實(shí)值只能有一個(gè)鍵值,但是一個(gè)鍵值可以對(duì)應(yīng)多 個(gè)真實(shí)值),這樣可以快速在數(shù)組等里面存取數(shù)據(jù)。

    看下圖:

    我們建立一個(gè)HashTable (哈希表),該表的長(zhǎng)度為N,然后我們分別在該表中的格子中存放 不同的元素。每個(gè)格子下面存放的元素又是以鏈表的方式存放元素。
    ? 當(dāng)添加一個(gè)新的元素Entry的時(shí)候,首先我們通過(guò)一個(gè)Hash函數(shù)計(jì)算出這個(gè)Entry元 素的Hash值hashcode。通過(guò)該hashcode值,就可以直接定位出我們應(yīng)該把這個(gè)Entry 元素存入到Hash表的哪個(gè)格子中,如果該格子中己經(jīng)存在元素了,那么只要把新的 Entry 元存放到這個(gè)鏈表中即可。
    ? 如果要查找一個(gè)元素Entry的時(shí)候,也同樣的方式,通過(guò)Hash函數(shù)計(jì)算出這個(gè)Entry 元素的Hash值hashcode。然后通過(guò)該hashcode值,就可以直接找到這個(gè)Entry是存放到哪個(gè)格子中的。接下來(lái)就對(duì)該格子存放的鏈表元素進(jìn)行逐個(gè)的比較查找就可以了。
    舉一個(gè)比較簡(jiǎn)單的例子來(lái)說(shuō)明這個(gè)算法的運(yùn)算方式:
    假定我們有一個(gè)長(zhǎng)度為8的Hash表(可以理解為一個(gè)長(zhǎng)度為8的數(shù)組)。在這個(gè)Hash表中 存放數(shù)字:如下表

    假定我們的Hash函數(shù)為:
    Hashcode = X%8 ,即對(duì) 8 取余數(shù)。
    其中X就是我們需要放入Hash表中的數(shù)字,而這個(gè)函數(shù)返回的Hashcode就是Hash碼。
    假定我們有下面10個(gè)數(shù)字需要依次存入到這個(gè)Hash表中:
    11, 23, 44, 9, 6, 32, 12, 45, 57, 89
    通過(guò)上面的Hash函數(shù),我們可以得到分別對(duì)應(yīng)的Hash碼:
    11 -- 3; 23 -- 7;44 -- 4;9 -- 1;6 -- 6;32 -- 0;12 -- 4;45 -- 5;57 -- 1;89 -- 1;
    計(jì)算出來(lái)的Hash碼分別代表該數(shù)字應(yīng)該存放到Hash表中的哪個(gè)對(duì)應(yīng)數(shù)字的格子中。如果改格子中已經(jīng)有數(shù)字存在了,那么就以鏈表的方式將數(shù)字依次存放在該格子中,如下表:

    Hash 表和 Hash 算法的特點(diǎn)就是它的存取速度比數(shù)組差一些,但是比起單純的鏈表,在查找和存儲(chǔ)方面卻要好很多。同時(shí)數(shù)組也不利于數(shù)據(jù)的重構(gòu)和排序等方面的要求。
    更具體的說(shuō)明,讀者可以參考數(shù)據(jù)結(jié)構(gòu)相關(guān)方面的書籍。

    簡(jiǎn)單的了解了一下Hash算法后,我們就來(lái)看看HashMap的屬性有哪些:

    public class HashMap extends AbstractMap implements Map, Cloneable, Serializable {/* * 哈希表,Entry對(duì)象中存放的是犍值對(duì)。并且該數(shù)組的長(zhǎng)度為2的次方 * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry[] table; /* * 鍵值對(duì)的個(gè)數(shù) * The number of key-value mappings contained in this identity hash map? */ transient int size; /* * The load factor for the hash table. * 哈希表的負(fù)載因子 * @serial * / final float loadFactor;

    里面最重要的3個(gè)屬性:

  • transient Entry [] table:用來(lái)存放鍵值對(duì)的對(duì)象Entry數(shù)組,也就是Hash 表
  • transient int size:當(dāng)前Map中存放的鍵值對(duì)的個(gè)數(shù)
  • final float loadFactor:負(fù)載因子,用來(lái)決定什么情況下應(yīng)該對(duì)Entry進(jìn)行擴(kuò)容
  • Entry對(duì)象是Map接口中的一個(gè)內(nèi)部接口。即使用它來(lái)保存鍵值對(duì)。 我們看看這個(gè)Entry內(nèi)部接口在HashMap中的實(shí)現(xiàn):

    static class Entry implements Map.Entry { final Object key; // 鍵,并且不可修改 Object value; // 值 final int hash; // hash碼 Entry next; // 當(dāng)前鍵值對(duì)的下一個(gè)鍵值對(duì) Entry (int h, Object k, Object v, Entry n) { value = v; next =n; key = k; hash = h; }

    通過(guò)查看源碼,我們可以看到Entry類有點(diǎn)類似一個(gè)單向鏈表。其中:
    final Object key 和 Object value存放的就是我們放入Map中的鍵值對(duì)。 而屬性Entry next表示當(dāng)前鍵值對(duì)的下一個(gè)鍵值對(duì)。
    接下來(lái),我們看看 HashMap 的主要的構(gòu)造函數(shù):

    public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // 默認(rèn)負(fù)載因子為0.75threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 默認(rèn)初始化hash表長(zhǎng)度為16init();} public HashMap(int initialCapacity,float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity:" + initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor:" + loadFactor);// Find a power of 2 >= initialCapacity// 找出第一個(gè)大于initialCapacity的2次方的數(shù)為初始化hash表的長(zhǎng)度int capacity = 1;while (capacity < initialCapacity)capacity <<= 1;// 負(fù)載因子this.loadFactor = loadFactor;// threshold表示hash表什么長(zhǎng)度的時(shí)候需要重構(gòu)threshold = (int)(capacity * loadFactor);table = new Entry[capacity]; // 構(gòu)建Entry數(shù)組作為hash表init();}public HashMap(int initialCapacity) {// 默認(rèn)負(fù)載因子為0.75this(initialCapacity,DEFAULT_LOAD_FACTOR);}

    我們主要看看 public HashMap (int initialcapacity, float loadFactor) 因?yàn)?#xff0c;另外兩個(gè)構(gòu)造函數(shù)實(shí)際也是同樣的方式進(jìn)行構(gòu)建一個(gè)HashMap的。 該構(gòu)造函數(shù):

  • 首先是判斷參數(shù)int initialCapacity和float loadFactor是否合法
  • 然后確定Hash表的初始化長(zhǎng)度。確定的策略是:通過(guò)傳進(jìn)來(lái)的參數(shù) initialCapacity來(lái)找出第一個(gè)大于它的2的次方的數(shù)。比如說(shuō)我們傳了 18 這樣的一個(gè)initialCapacity參數(shù),那么真實(shí)的table數(shù)組的長(zhǎng)度為2的5 次方,即32。之所以采用這種策略來(lái)構(gòu)建Hash表的長(zhǎng)度,是因?yàn)?的次方的運(yùn)算對(duì)于現(xiàn)代的處理器來(lái)說(shuō),可以通過(guò)一些方法得到更加好的執(zhí)行效率
  • 接下來(lái)就是得到重構(gòu)因子(threshold) 了,這個(gè)屬性也是HashMap中的一個(gè)比較重要的屬性,它表示,當(dāng)Hash表中的元素被存放了多少個(gè)之后,我們就需要對(duì)該 Hash 表進(jìn)行重構(gòu)。
  • 最后就是使用得到的初始化參數(shù)capacity來(lái)構(gòu)建Hash表:Entry[ ] table。 下面我們看看一個(gè)鍵值對(duì)是如何添加到 HashMap 中的。
  • public Object put(Object key, Object value) { // 如果key為null則使用一個(gè)常量來(lái)代替該key值 Object k = maskNull(key); int hash = hash(k); // 計(jì)算key值的hash碼 // 通過(guò)使用hash碼來(lái)定位,我們應(yīng)該把當(dāng)前的鍵值對(duì)存放到hash表中的哪個(gè)格子中 int i = indexFor(hash, table.length); // 計(jì)算出為第i個(gè)格子 // 遍歷當(dāng)前的Hash表table[i]格中的鏈表 for (Entry e = table[i]; e != null;e = e.next()) {// 判斷當(dāng)前的Hash表table[i]格中的鏈表,是否已經(jīng)存在相同的key的鍵值對(duì)if (e.hash == hash && eq(k,e.key)) {// 如果存在一樣的key,那么把新的value覆寫老的value,并把老的value返回Object oldValue = e.value;e.value = value;e.recordAccess(this); // 值(value)覆寫事件,HashMap中該方法沒(méi)有做任何處理return oldValue; } }modCount++; // 計(jì)數(shù)器+1 // 如果遍歷后發(fā)現(xiàn)沒(méi)有存在相同的鍵(key),那么就增加當(dāng)前的鍵值對(duì)到hash表中 addEntry(hash,k,value,i); return null; }

    該put方法是用來(lái)添加一個(gè)鍵值對(duì)(key-value)到Map中,如果Map中己經(jīng)存在相同的 鍵的鍵值對(duì)的話,那么就把新的值覆蓋老的值,并把老的值返回給方法的調(diào)用者。如果不存在一樣的鍵,那么就返回 null 。我們看看方法的具體實(shí)現(xiàn):

  • 首先我們判斷如果key為null則使用一個(gè)常量來(lái)代替該key值,該行為在方法maskNull()中將key替換為一個(gè)非null的對(duì)象k。
  • 計(jì)算 key 值的 Hash 碼:hash
  • 通過(guò)使用Hash碼來(lái)定位,我們應(yīng)該把當(dāng)前的鍵值對(duì)存放到Hash表中的哪個(gè)格子中。 indexFor()方法計(jì)算出的結(jié)果i就是Hash表(table)中的下標(biāo)。
  • 然后遍歷當(dāng)前的Hash表中table[i]格中的鏈表。從中判斷是否存在一樣的鍵(Key) 的鍵值對(duì)。如果存在一樣的key,那么就用新的value覆寫老的value,并把老的value 返回
  • 如果遍歷后發(fā)現(xiàn)不存在同樣的鍵,那么就增加當(dāng)前鍵值對(duì)到Hash表中的第i個(gè)格子中的鏈表中。并返回 null。
  • 最后我們看看一個(gè)鍵值對(duì)是如何添加到各個(gè)格子中的鏈表中的:

    void addEntry(int hash, Object key, Object value, int bucketIndex) { // 創(chuàng)建一個(gè)Entry對(duì)象用來(lái)存放鍵值對(duì) // 并把原來(lái)的格子中鏈表的第一位置的元素作為當(dāng)前Entry對(duì)象的下一個(gè)元素 // 換句話說(shuō),就是把當(dāng)前創(chuàng)建的Entry對(duì)象,加到鏈表的第一個(gè)位置,其它的掛到它的后面 table[bucketIndex] = new Entry(hash, k, value, table[bucketIndex]); // 如果存放元素的個(gè)數(shù)大于重構(gòu)因子threshold,那么就進(jìn)行重構(gòu) if (size++ >= threshold) {resize(2 * table.length); }

    我們先看void addEntry(int hash, Obj ect key, Obj ect value, int bucketIndex)方法,該方法的作用就用來(lái)添加一個(gè)鍵值對(duì)到Hash表的第bucketIndex 個(gè)格子中的鏈表中去。這個(gè)方法的工作就是:

  • 創(chuàng)建一個(gè) Entry 對(duì)象用來(lái)存放鍵值對(duì)
  • 添加該鍵值對(duì) Entry 對(duì)象到鏈表中
  • 最后在size屬性+1,并判斷是否需要對(duì)當(dāng)前的Hash表進(jìn)行重構(gòu)。如果需要就在 void resize (int newCapacity)方法中進(jìn)行重構(gòu)。
  • 之所以需要重構(gòu),也是基于性能考慮。大家可以考慮這樣一種情況,假定我們的Hash 表只有4個(gè)格子,那么我們所有的數(shù)據(jù)都是放到這4個(gè)格子中。如果存儲(chǔ)的數(shù)據(jù)量比較大 的話,例如 100。這個(gè)時(shí)候,我們就會(huì)發(fā)現(xiàn),在這個(gè) Hash 表中的 4 個(gè)格子存放的 4 個(gè)長(zhǎng)長(zhǎng)的鏈表。而我們每次查找元素的時(shí)候,其實(shí)相當(dāng)于就是遍歷鏈表了。這種情況下,我們用 這個(gè)Hash表來(lái)存取數(shù)據(jù)的性能實(shí)際上和使用鏈表差不多了。

    但是如果我們對(duì)這個(gè) Hash 表進(jìn)行重構(gòu),換為使用 Hash 表長(zhǎng)度為 200 的表來(lái)存儲(chǔ)這 100個(gè)數(shù)據(jù),那么平均 2個(gè)格子里面才會(huì)存放一個(gè)數(shù)據(jù)。這個(gè)時(shí)候我們查找的數(shù)據(jù)的速度就會(huì)非常的快。因?yàn)榛旧厦總€(gè)格子中存放的鏈表都不會(huì)很長(zhǎng),所以我們遍歷鏈表的次數(shù)也 就很少,這樣也就加快了查找速度。但是這個(gè)時(shí)候又存在了另外的一個(gè)問(wèn)題。我們使用了至 少200個(gè)數(shù)據(jù)的空間來(lái)存放100個(gè)數(shù)據(jù),這樣就造成至少100個(gè)數(shù)據(jù)空間的浪費(fèi)。 在速 度和空間上面,我們需要找到一個(gè)適合自己的中間值。在HashMap中我們通過(guò)負(fù)載因子 (loadFactor)來(lái)決定應(yīng)該什么時(shí)候重構(gòu)我們的Hash 表,以達(dá)到比較好的性能狀態(tài)。
    我們?cè)倏纯粗貥?gòu)Hash表的方法:void resize( int newCapacity)是如何實(shí)現(xiàn)的:

    // 該方法用來(lái)重構(gòu)Hash表 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) {// 如果Hash表的長(zhǎng)度已經(jīng)達(dá)到最大值,那么就不進(jìn)行重構(gòu)了threshold = Integer.MAX_VALUE;return; } // 構(gòu)建新的Hash表 Entry[] newTable = new Entry[newCapacity]; // 將所有老的Hash表中的元素都轉(zhuǎn)移存放到新的Hash表newTable中 transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }

    它的實(shí)現(xiàn)方式比較簡(jiǎn)單:

  • 首先判斷如果Hash表的長(zhǎng)度己經(jīng)達(dá)到最大值,那么就不進(jìn)行重構(gòu)了。因?yàn)檫@個(gè)時(shí)候Hash表的長(zhǎng)度己經(jīng)達(dá)到上限,己經(jīng)沒(méi)有必要重構(gòu)了。
  • 然后就是構(gòu)建新的 Hash 表
  • 把老的Hash表中的對(duì)象數(shù)據(jù)全部轉(zhuǎn)移到新的Hash表newTable中,并設(shè)置新的重構(gòu)因子threshold
  • 對(duì)于HashMap中的實(shí)現(xiàn)原理,我們就分析到這里。大家可能會(huì)發(fā)現(xiàn),HashCode的計(jì)算, 是用來(lái)定位我們的鍵值對(duì)應(yīng)該放到 Hash 表中哪個(gè)格子中的關(guān)鍵屬性。 而這個(gè) HashCode的計(jì)算方法是調(diào)用的各個(gè)對(duì)象自己的實(shí)現(xiàn)的hashCode()方法。而這個(gè)方法是 在Object對(duì)象中定義的,所以我們自己定義的類如果要在集合中使用的話,就需要正確 的覆寫 hashCode() 方法。下面就介紹一下應(yīng)該如何正確覆寫 hashCode() 方法。

    1.4.5 覆寫 hashCode()

    在明白了 HashMap具有哪些功能,以及實(shí)現(xiàn)原理后,了解如何寫一個(gè)hashCode () 方法就更有意義了。當(dāng)然,在HashMap中存取一個(gè)鍵值對(duì)涉及到的另外一個(gè)方法為equals (),因?yàn)樵摲椒ǖ母矊懺诟呒?jí)特性已經(jīng)講解了。這里就不做過(guò)多的描述。
    設(shè)計(jì)hashCode ()時(shí)最重要的因素就是:無(wú)論何時(shí),對(duì)同一個(gè)對(duì)象調(diào)用hashCode () 都應(yīng)該生成同樣的值。如果在將一個(gè)對(duì)象用 put() 方法添加進(jìn) HashMap 時(shí)產(chǎn)生一個(gè) hashCode ()值,而用get ()取出時(shí)卻產(chǎn)生了另外一個(gè)hashCode ()值,那么就無(wú)法重新 取得該對(duì)象了。所以,如果你的hashCode()方法依賴于對(duì)象中易變的數(shù)據(jù),那用戶就要 小心了,因?yàn)榇藬?shù)據(jù)發(fā)生變化時(shí), hashCode() 就會(huì)產(chǎn)生一個(gè)不同的 hash 碼,相當(dāng)于產(chǎn)生 了一個(gè)不同的“鍵”。
    此外,也不應(yīng)該使 hashCode() 依賴于具有唯一性的對(duì)象信息,尤其是使用 this 的 值,這只能產(chǎn)生很糟糕的hashCode()。因?yàn)檫@樣做無(wú)法生成一個(gè)新的“鍵",使之與put() 種原始的"鍵值對(duì)”中的“鍵”相同。例如,如果我們不覆寫Object的hashCode()方 法,那么調(diào)用該方法的時(shí)候,就會(huì)調(diào)用Object的hashCode ()方法的默認(rèn)實(shí)現(xiàn)。Object 的hashCode ()方法,返回的是當(dāng)前對(duì)象的內(nèi)存地址。下次如果我們需要取一個(gè)一樣的“鍵” 對(duì)應(yīng)的鍵值對(duì)的時(shí)候,我們就無(wú)法得到一樣的 hashCode 值了。因?yàn)槲覀兒髞?lái)創(chuàng)建的“鍵” 對(duì)象己經(jīng)不是存入HashMap中的那個(gè)內(nèi)存地址的對(duì)象了。
    我們看一個(gè)簡(jiǎn)單的例子,就能更加清楚的理解上面的意思。假定我們寫了一個(gè)類: Person (人),我們判斷一個(gè)對(duì)象“人”是否指向同一個(gè)人,只要知道這個(gè)人的身份證 號(hào)一直就可以了。
    先看我們沒(méi)有實(shí)現(xiàn) hashCode 的情況:

    package c08.hashEx; import java.util.*; //身份證類 class Code{ final int id;//身份證號(hào)碼已經(jīng)確認(rèn),不能改變 Code(int i ) { id=i; } //身份號(hào)碼相同,則身份證相同 public boolean equals(Object anObject) { if (anObject instanceof Code) { Code other = (Code)anObject; return this.id == other.id; } return false; }public String toString() { return "身份證:"+id; } }//人員信息類 class Person { Code id; //身份證 String name; // 姓名 public Person(String name, Code id) { this.id = id; this.name = name; } //如果身份證號(hào)相同,就表示兩個(gè)人是同一個(gè)人 public boolean equals(Object anObject) { if (anObject instanceof Person){ Person other=(Person) anObject; return this.id.equals(other.id); } return false; } public String toString() { return "姓名:" + name + "身份證:" + id.id + "\n"; } } public class HashCodeEx { public static void main(String[] args) { HashMap map = new HashMap(); Person p1 = new Person("張三",new Code (123)); map.put(p1.id,p1) ;//我們根據(jù)身份證來(lái)作為key值存放到Map中 Person p2 = new Person("李四", new Code(456)); map.put(p2.id,p2); Person p3 = new Person("王二"new Code (789)); map.put(p3.id,p3); System. out .println("HashMap中存放的人員信息:\n"+map); //張三改名為:張山 但是還是同一個(gè)人。 Person p4 = new Person ("張山",new Code (123)); map.put(p4.id,p4); System.out.println("張三改名后HashMap中存放的人員信息:\n"+map); //查找身份證為:123的人員信息 System.out.println ("查找身份證為:123的人員信息:"+map.get(new Code(123))); } } 運(yùn)行結(jié)果為: HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:123=姓名:張三 身份證:123 ,身份證:789=姓名:王二 身份證:789 } 張三改名后HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:123=姓名:張三 身份證:123 ,身份證:123=姓名:張山 身份證:123 ,身份證:789=姓名:王二 身份證:789 } 查找身份證為:123的人員信息:null

    上面的例子的演示的是,我們?cè)谝粋€(gè)HashMap中存放了一些人員的信息。并以這些人 員的身份證最為人員的“鍵”。當(dāng)有的人員的姓名修改了的情況下,我們需要更新這個(gè) HashMap。同時(shí)假如我們知道某個(gè)身份證號(hào),想了解這個(gè)身份證號(hào)對(duì)應(yīng)的人員信息如何, 我們也可以根據(jù)這個(gè)身份證號(hào)在HashMap中得到對(duì)應(yīng)的信息。

    而例子的輸出結(jié)果表示,我們所做的更新和查找操作都失敗了。失敗的原因就是我們的 身份證類: Code 沒(méi)有覆寫 hashCode() 方法。這個(gè)時(shí)候,當(dāng)查找一樣的身份證號(hào)碼的鍵 值對(duì)的時(shí)候,使用的是默認(rèn)的對(duì)象的內(nèi)存地址來(lái)進(jìn)行定位。這樣,后面的所有的身份證號(hào)對(duì) 象new Code (123)產(chǎn)生的hashCode ()值都是不一樣的。所以導(dǎo)致操作失敗。
    下面,我們給Code類加上hashCode ()方法,然后再運(yùn)行一下程序看看:

    //身份證類 class Code{ final int id;//身份證號(hào)碼已經(jīng)確認(rèn),不能改變 Code(int i ) { id=i; } //身份號(hào)碼相同,則身份證相同 public boolean equals(Obj ect anObject) { if (anObject instanceof Code) { Code other = (Code)anObject; return this.id == other.id; } return false; } public String toString() { return "身份證:"+id; } //覆寫hashCode方法,并使用身份證號(hào)作為hash值 public int hashCode(){ return id; } }

    再次執(zhí)行上面的HashCodeEx的結(jié)果就為:

    HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:789=姓名:王二 身份證:789 ,身份證:123=姓名:張三 身份證:123 } 張三改名后HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:789=姓名:王二 身份證:789 ,身份證:123=姓名:張山 身份證:123 } 查找身份證為:123的人員信息:姓名:張山身份證:123

    這個(gè)時(shí)候,我們發(fā)現(xiàn)。我們想要做的更新和查找操作都成功了。

    對(duì)于Map部分的使用和實(shí)現(xiàn),主要就是需要注意存放“鍵值對(duì)”中的對(duì)象的equals () 方法和 hashCode() 方法的覆寫。如果需要使用到排序的話,那么還需要實(shí)現(xiàn) Comparable 接口中的compareTo ()方法。我們需要注意Map中的“鍵”是不能重復(fù)的,而是否重復(fù) 的判斷,是通過(guò)調(diào)用“鍵”對(duì)象的equals ()方法來(lái)決定的。而在HashMap中查找和存 取"鍵值對(duì)”是同時(shí)使用hashCode ()方法和equals ()方法來(lái)決定的。

    1.5 Set

    1.5.1 概述

    Java中的Set和正好和數(shù)學(xué)上直觀的築(set)的概念是相同的。Set最大的特性就是 不允許在其中存放的元素是重復(fù)的。根據(jù)這個(gè)特點(diǎn),我們就可以使用Set這個(gè)接口來(lái)實(shí)現(xiàn) 前面提到的關(guān)于商品種類的存儲(chǔ)需求。 Set 可以被用來(lái)過(guò)濾在其他集合中存放的元素,從 而得到一個(gè)沒(méi)有包含重復(fù)新的集合。

    1.5.2 常用方法

    按照定義, Set 接口繼承 Collection 接口,而且它不允許集合中存在重復(fù)項(xiàng)。所 有原始方法都是現(xiàn)成的,沒(méi)有引入新方法。具體的 Set 實(shí)現(xiàn)類依賴添加的對(duì)象的 equals() 方法來(lái)檢查等同性。

    我們簡(jiǎn)單的描述一下各個(gè)方法的作用:
    ? public int size():返回set中元素的數(shù)目,如果 set 包含的元素?cái)?shù)大于 Integer.MAX_VALUE,返回 Integer.MAX_VALUE
    ? public boolean isEmpty() :如果 set 中不含元素,返回 true
    ? public boolean contains(Object o) :如果 set 包含指定元素,返回 true
    ? public Iterator iterator():返回 set 中元素的迭代器,元素返回沒(méi)有特定的順序
    ? public Object[] toArray() :返回包含 set 中所有元素的數(shù)組
    ? public Object[] toArray(Object[] a):返回包含set中所有元素的數(shù)組,返回?cái)?shù)組的運(yùn) 行時(shí)類型是指定數(shù)組的運(yùn)行時(shí)類型
    ? public boolean add(Object o):如果set中不存在指定元素,則向set加入
    ? public booleanremove(Objecto):如果set中存在指定元素,則從set中刪除
    ? public boolean removeAll(Collection c):如果 set 包含指定集合,則從 set 中刪除指 定集合的所有元素
    ? public boolean containsAll(Collection c):如果 set 包含指定集合的所有元素,返回 true。如果指定集合也是一個(gè)set,只有是當(dāng)前set的子集時(shí),方法返回true
    ? public boolean addAll(Collection c):如果 set 中中不存在指定集合的元素,則向 set 中加入所有元素
    ? public boolean retainAll(Collection c):只保留 set 中所含的指定集合的元素(可選操 作)。換言之,從set中刪除所有指定集合不包含的元素。如果指定集合也是一個(gè) set,那么該操作修改set的效果是使它的值為兩個(gè)set的交集
    ? public boolean removeAll(Collection c):如果 set 包含指定集合,則從 set 中刪除指 定集合的所有元素
    ? public void clear():從 set 中刪除所有元素

    “集合框架” 支持 Set 接口兩種普通的實(shí)現(xiàn):
    HashSet 和 TreeSet 以及 LinkedHashSet。 下表中是Set的常用實(shí)現(xiàn)類的描述:

    在更多情況下,您會(huì)使用 HashSet 存儲(chǔ)重復(fù)自由的集合。同時(shí) HashSet 中也是采用 了 Hash 算法的方式進(jìn)行存取對(duì)象元素的。所以添加到 HashSet 的對(duì)象對(duì)應(yīng)的類也需要 采用恰當(dāng)方式來(lái)實(shí)現(xiàn) hashCode() 方法。雖然大多數(shù)系統(tǒng)類覆蓋了 Object 中缺省的 hashCode() 實(shí)現(xiàn), 但創(chuàng)建您自己的要添加到 HashSet 的類時(shí), 別忘了覆蓋 hashCode() 。

    對(duì)于Set的使用,我們先以一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明:

    import java.util.*; public class HashSetDemo { public static void main(String[] args) { Set set1 = new HashSet(); if (set1.add("a")) {//添加成功 System.out.println("1 add true"); } if (set1.add("a")) {//添加失敗 System.out.println("2 add true"); } set1.add("000") ; //添加對(duì)象到Set集合中 set1.add("111"); set1.add("222"); System.out.println("集合set1的大小: " + set1.size()); System.out.println("集合set1 的內(nèi)容:" + set1); set1.remove("000") ;//從集合set1中移除掉 "000" 這個(gè)對(duì)象 System.out.println("集合set1 移除 000 后的內(nèi)容:" + set1); System.out.println("集合set1中是否包含000 :" + set1.contains("000")); System.out.println ("集合set1中是否包含 111 :" + set1.contains("111")); Set set2 = new HashSet(); set2.add("111"); set2.addAll(set1); // 將set1集合中的元素全部都加到set2中 System.out.println("集合set2的內(nèi)容:"+set2 ); set2.clear(); // 清空集合set1中的元素 System.out.println("集合set2是否為空:" + set2.isEmpty()); Iterator iterator = set1.iterator(); // 得到一個(gè)迭代器 while (iterator. hasNext()) { Object element = iterator.next(); System.out.println("iterator = " + element); } // 將集合set1轉(zhuǎn)化為數(shù)組 Obj ect s[]= set].toArray(); for(int i=0;i<s.length;i++){ System.out.println(s[i]); } } }

    程序執(zhí)行的結(jié)果為:

    1 add true 集合set]的大小:4 集合 set]的內(nèi)容:[222, a, 000, ]]]] 集合set]移除000后的內(nèi)容:[222, a, ]]]] 集合set]中是否包含000 : false 集合set]中是否包含]]]:true 集合 set2 的內(nèi)容:[222, a, ]]]] 集合set2是否為空:true iterator = 222 iterator = a iterator =]]] 222 a 111

    從上面的這個(gè)簡(jiǎn)單的例子中,我們可以發(fā)現(xiàn),Set中的方法與直接使用Collection中的 方法一樣。唯一需要注意的就是Set中存放的元素不能重復(fù)。

    我們?cè)倏匆粋€(gè)例子,來(lái)了解一下其它的Set的實(shí)現(xiàn)類的特性:

    package c08; import java.util.*; public class SetSortExample { public static void main(String args[]) { Set set1 = new HashSet(); Set set2 = new LinkedHashSet(); for (int i = 0; i < 5; i++) { //產(chǎn)生一個(gè)隨機(jī)數(shù),并將其放入Set中 int s = (int)(Math.random() * 100); set1.add(new Integer(s)); set2.add(new Integer(s)); System.out.println ("第 " + i + " 次隨機(jī)數(shù)產(chǎn)生為:" + s); } System.out.println("未排序前HashSet : " + set1); System.out.println ("未排序前LinkedHashSet: " + set2); // 使用TreeSet來(lái)對(duì)另外的Set進(jìn)行重構(gòu)和排序 Set sortedSet = new TreeSet(set1); System.out.println("排序后TreeSet : " + sortedSet); } }

    該程序的一次執(zhí)行結(jié)果為:

    第0次隨機(jī)數(shù)產(chǎn)生為:96 第1次隨機(jī)數(shù)產(chǎn)生為:64 第2次隨機(jī)數(shù)產(chǎn)生為:14 第3次隨機(jī)數(shù)產(chǎn)生為:95 第4次隨機(jī)數(shù)產(chǎn)生為:57 未排序前 HashSet:[64, 96, 95, 57, 14] 未排序前LinkedHashSet:[96, 64, 14, 95, 57 ] 排序后TreeSet:[14, 57, 64, 95, 96]

    從這個(gè)例子中,我們可以知道HashSet的元素存放順序和我們添加進(jìn)去時(shí)候的順序沒(méi) 有任何關(guān)系,而 LinkedHashSet 則保持元素的添加順序。 TreeSet 則是對(duì)我們的 Set 中的元素進(jìn)行排序存放。

    一般來(lái)說(shuō),當(dāng)您要從集合中以有序的方式抽取元素時(shí), TreeSet 實(shí)現(xiàn)就會(huì)有用處。為 了能順利進(jìn)行,添加到 TreeSet 的元素必須是可排序的。 而您同樣需要對(duì)添加到 TreeSet 中的類對(duì)象實(shí)現(xiàn) Comparable 接口的支持。對(duì)于 Comparable 接口的實(shí)現(xiàn),在 前一小節(jié)的Map中己經(jīng)簡(jiǎn)單的介紹了一下。我們暫且假定一棵樹知道如何保持java.lang 包裝程序器類元素的有序狀態(tài)。一般說(shuō)來(lái),先把元素添加到HashSet,再把集合轉(zhuǎn)換為 TreeSet 來(lái)進(jìn)行有序遍歷會(huì)更快。這點(diǎn)和 HashMap 的使用非常的類似。

    其實(shí)Set的實(shí)現(xiàn)原理是基于Map上面的。通過(guò)下面我們對(duì)Set的進(jìn)一步分析大家就能 更加清楚的了解這點(diǎn)了。

    1.5.3 實(shí)現(xiàn)原理

    Java中Set的概念和數(shù)學(xué)中的集合(set)一致,都表示一個(gè)集內(nèi)可以存放的元素是不能重 復(fù)的。
    前面我們會(huì)發(fā)現(xiàn),Set中很多實(shí)現(xiàn)類和Map中的一些實(shí)現(xiàn)類的使用上非常的相似。而且 前面再講解Map的時(shí)候,我們也提到:Map中的“鍵值對(duì)”,其中的“鍵”是不能重復(fù)的。 這個(gè)和Set中的元素不能重復(fù)一致。我們以HashSet為例來(lái)分析一下,會(huì)發(fā)現(xiàn)其實(shí)Set利用 的就是Map中“鍵”不能重復(fù)的特性來(lái)實(shí)現(xiàn)的。
    先看看HashSet中有哪些屬性:

    public class HashSet extends AbstractSet implements Set, Cloneable, java.io.Serializable {static final long serialVersionUID = -5024744406713321676L;// 核心屬性:HashMapprivate transient HashMap map;// 常量:present用來(lái)所有Map中“鍵值對(duì)”的“值”// Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object();

    再結(jié)合構(gòu)造函數(shù)來(lái)看看:

    /* * 構(gòu)造函數(shù)就是對(duì)HashMap的構(gòu)建 * Constructs a new, empty set;the backing <tt>HashMap</tt> * default initial capacity(16) and load factor(0.75). */public HashSet() {map = new HashMap(); }public HashSet(int initialCapacity, float loadFactor) {map = new HashMap(initialCapacity,loadFactor);}public HashSet(int initialCapacity) {map = new HashMap(initialCapacity); }

    通過(guò)這些方法,我們可以發(fā)現(xiàn),其實(shí)HashSet的實(shí)現(xiàn),全部的操作都是基于HashMap來(lái)進(jìn)行的。我們看看是如何通過(guò)HashMap來(lái)保證我們的HashSet的元素不重復(fù)性的:

    /* * Adds the specified element to this set if it is not already contain the specified element. * */ public boolean add(Object o) {return map.put(o,PRESENT) == null; }

    看到這個(gè)操作我們可以發(fā)現(xiàn)HashSet的巧妙實(shí)現(xiàn):就是建立一個(gè)“鍵值對(duì)”,“鍵”就是 我們要存入的對(duì)象,“值”則是一個(gè)常量。這樣可以確保,我們所需要的存儲(chǔ)的信息之是“鍵”。 而“鍵”在Map中是不能重復(fù)的,這就保證了我們存入Set中的所有的元素都不重復(fù)。而 判斷是否添加元素成功,則是通過(guò)判斷我們向Map中存入的“鍵值對(duì)”是否己經(jīng)存在,如 果存在的話,那么返回值肯定是常量: PRESENT,表示添加失敗。如果不存在,返回值就 為null `表示添加成功。

    我們?cè)倏纯雌渌姆椒▽?shí)現(xiàn):

    /* * Removes the specified element from this set if it is present * 移出操作即是對(duì)Map中的鍵值對(duì)進(jìn)行移出,如果返回值為PRESENT常量,則表示移出成功 * @param o object to be removed from this set, if present. * @return <tt>true</tt> if the set contained the specified element. * */public boolean remove(Object o) {return map.remove(o) == PRESENT; }public int size() {return map.size(); }public boolean contains(Object o) {return map.containsKey(o); }public boolean isEmpty() {return map.isEmpty(); }public Iterator iterator() {return map.keySet().iterator(); }

    了解了這些后,我們就不難理解,為什么HashMap中需要注意的地方,在HashSet中 也同樣的需要注意。其他的Set的實(shí)現(xiàn)類也是差不多的原理。
    至此對(duì)于Set我們就應(yīng)該能夠比較好的理解了。

    1.6 總結(jié):集合框架中常用類比較

    用“集合框架”設(shè)計(jì)軟件時(shí),記住該框架四個(gè)基本接口的下列層次結(jié)構(gòu)關(guān)系會(huì)有用處:

    • Collection接口是一組允許重復(fù)的對(duì)象。
    • Set接口繼承Collection,但不允許重復(fù)。
    • List接口繼承Collection,允許重復(fù),并引入位置下標(biāo)。
    • Map 接口既不繼承 Set 也不繼承 Collection, 存取的是鍵值對(duì)

    我們以下面這個(gè)圖表來(lái)描述一下常用的集合的實(shí)現(xiàn)類之間的區(qū)別:

    2 練習(xí)

  • 撰寫一個(gè)Person class,表示一個(gè)人員的信息。令該類具備多輛Car的信息,表示一個(gè)人可以擁有的車子的數(shù)據(jù)
  • 屬性/方法:
    Certificate code: 身份證對(duì)象
    name: 姓名
    cash: 現(xiàn)金
    List car: 擁有的汽車,其中存放的是Car對(duì)象
    boolean buycar(car):買車子
    boolean sellcar(Person p):把自己全部的車子賣給別人
    boolean buyCar(Car car,Person p):自動(dòng)查找賣車的人p是否有買主想要買的車car,如果有就買,并返回true, 否則返回false
    viod addCar(car):把某輛車送給方法的調(diào)用者。
    String toString():得到人的信息

  • 并撰寫第二個(gè) Car class
  • 屬性/方法:
    String ID:ID 車牌號(hào)
    cost:價(jià)格
    color:顏色
    Person owner:車子的擁有者
    to String():得到汽車的信息
    equals():比較車子是否同一倆汽車,ID相同則認(rèn)為相同

  • 在另外一個(gè)Market類里面,進(jìn)行車子的買賣。并保留所有交易人員的的信息到一個(gè)HashMap中,我們可以通過(guò)身份證號(hào)來(lái)查找到對(duì)應(yīng)的人員的信息。同時(shí)所有的車子種類都在市場(chǎng)中進(jìn) 行注冊(cè),即車子的信息使用一個(gè)Set來(lái)保存
  • 屬性/方法:
    HashMap people:存放交易人員的信息。Key為身份證號(hào),value為Person對(duì)象
    static boolean sellCar(Person p1 ,Car car1, Person p2):p1 將 car1 賣給 p2 。并在該方法中記錄效益人的信息到 people 中。

  • 撰寫類Certificate 表示身份證
  • 屬性/方法:

    Id:號(hào)碼
    equals():比較兩個(gè)身份證是否同一個(gè),ID相同則認(rèn)為相同
    hashCode():正確編寫 hashCode 方法

    場(chǎng)景:
    一個(gè)叫Bob的人:身份證:310 現(xiàn)金:30000。
    有一輛車子:ID:001,紅色,價(jià)格:50000的車子;
    一個(gè)叫 Tom 的人:身份證: 210 現(xiàn)金: 70000,
    有一輛車子:顏色:白色,ID:003,價(jià)格:25000。
    一個(gè)叫 King 的人:身份證: 245 現(xiàn)金: 60000,
    有2輛車子:顏色:白色,ID:005,價(jià)格:18000。
    顏色:紅色,ID:045,價(jià)格:58000。

    Tom買了 Bob的車子.他就擁有了 2輛汽車
    King 把 ID=005 的車子買給了 Bob
    最后各人的信息如何?

    3 附錄:排序

    為了用’'集合框架"的額外部分把排序支持添加到Java 2 SDK,版本1.2,核心Java庫(kù) 作了許多更改。像 String 和 Integer 類如今實(shí)現(xiàn) Comparable 接口以提供自然排序 順序。對(duì)于那些沒(méi)有自然順序的類、或者當(dāng)您想要一個(gè)不同于自然順序的順序時(shí),您可以實(shí) 現(xiàn) Comparator 接口來(lái)定義您自己的。
    為了利用排序功能, '集合框架"提供了兩種使用該功能的接口: SortedSet 和 SortedMap 。

    Comparable 接口
    在 java.lang 包中, Comparable 接口適用于一個(gè)類有自然順序的時(shí)候。假定對(duì)象集合 是同一類型,該接口允許您把集合排序成自然順序。

    compareTo() 方法比較當(dāng)前實(shí)例和作為參數(shù)傳入的元素。如果排序過(guò)程中當(dāng)前實(shí)例出現(xiàn)在參數(shù)前,就返回某個(gè)負(fù)值。如果當(dāng)前實(shí)例出現(xiàn)在參數(shù)后,則返回正值。否則,返回零。這 里不要求零返回值表示元素相等。零返回值只是表示兩個(gè)對(duì)象排在同一個(gè)位置。

    在Java2SDK,版本1.2中有十四個(gè)類實(shí)現(xiàn)Comparable接口。下表展示了它們的自 然排序。雖然一些類共享同一種自然排序,但只有相互可比的類才能排序。

    創(chuàng)建您自己的類 Comparable 只是個(gè)實(shí)現(xiàn) compareTo() 方法的問(wèn)題。通常就是依賴幾 個(gè)數(shù)據(jù)成員的自然排序。您自己的類也應(yīng)該覆蓋 equals() 和 hashCode() 以確保兩個(gè) 相等的對(duì)象返回同一個(gè)散列碼。

    Comparator 接口

    若一個(gè)類不能用于實(shí)現(xiàn)java .lang .Comparable,您可以提供自己的 java.util.Comparator 行為。如果您不喜歡缺省的 Comparable 行為,您照樣可以 提供自己的 Comparator。

    Comparator 的 compare() 方法的返回值和 Comparable 的 compareTo() 方法的 返回值相似。在此情況下,如果排序時(shí)第一個(gè)元素出現(xiàn)在第二個(gè)元素之前,則返回一個(gè)負(fù)值。 如果第一個(gè)元素出現(xiàn)在后,那么返回一個(gè)正值。否則,返回零。與 Comparable 相似,零 返回值不表示元素相等。一個(gè)零返回值只是表示兩個(gè)對(duì)象排在同一位置。由 Comparator 用戶決定如何處理。如果兩個(gè)不相等的元素比較的結(jié)果為零,您首先應(yīng)該確信那就是您要的結(jié)果,然后記錄行為。

    為了演示,您會(huì)發(fā)現(xiàn)編寫一個(gè)新的忽略大小寫的Comparator,代替使用Collator進(jìn)行語(yǔ)言環(huán)境特定、忽略大小寫的比較會(huì)更容易。這樣的一種實(shí)現(xiàn)如下所示:

    class CaseInsensitiveComparator implements Comparator { public int compare(Object element1, Object element2) { String lowerE1 = ((String)element1).toLowerCase(); String lowerE2 = ((String)element2).toLowerCase(); return lowerE1.compareTo(lowerE2); } }

    因?yàn)槊總€(gè)類在某些地方都建立了 Object 子類,所以這不是您實(shí)現(xiàn) equals() 方法的必 要條件。實(shí)際上大多數(shù)情況下您不會(huì)去這樣做。切記該equals() 方法檢查的是 Comparator 實(shí)現(xiàn)的等同性,不是處于比較狀態(tài)下的對(duì)象。

    Collections類有個(gè)預(yù)定義的 Comparator 用于重用。調(diào)用Collections.reverseOrder() 返回一個(gè) Comparator, 它對(duì)逆序?qū)崿F(xiàn) Comparable 接口的對(duì)象進(jìn)行排序。

    總結(jié)

    以上是生活随笔為你收集整理的Java集合类原理详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。