對於相同類型的一組數據,雖然Java已經提供了數組加以表達,可是數組的結構實在太簡單了,第一它沒法直接添加新元素,第二它只能按照線性排列,故而數組用於基本的操做倒還湊合,若要用於複雜的處理就沒法勝任了。爲此Java設計了一大類的數據類型名叫容器,它們彷彿容納物品的器皿通常,可大可小,既能隨時往裏塞入新物件,又能隨時從中取出某物件。固然,依據不一樣的用途,容器也分爲好幾類,包括集合Set、映射Map、清單List等等,本文先從最基礎的集合開始介紹。
所謂集合,指的是一羣同類彙集在一塊兒,集合的最大特色就是裏面的每一個事物都是惟一的,即便重複加入也只算同一個元素。Java給集合分配的類型名稱叫作「Set」,在使用之時還得在Set後面補充一對尖括號,裏面填集合內部元素的數據類型。好比一個字符串集合,它的完整類型寫法爲「Set<String>」,下面即是聲明字符串集合變量的代碼例子:html
Set<String> set;
但是因爲Set實際上屬於接口,所以不能直接用來建立集合實例,在編程開發中,每每使用Set的兩個實現類HashSet和TreeSet。
HashSet的大名叫哈希集合,其內部採起哈希表來存儲數據;而TreeSet的大名叫作二叉集合,其內部採起二叉樹來存儲數據。儘管HashSet與TreeSet兩者的存儲結構不一樣,但它們在編碼調用時大致相似,因此接下來就以HashSet爲例,概要描述集合的基本用法。
一開始使用集合,固然也要先建立該集合的實例。建立集合實例的方式跟建立一個類的實例相同,都得調用它們的構造方法,集合實例的具體建立代碼以下所示:java
HashSet<String> set = new HashSet<String>();
有了集合實例,再經過實例去調用具體的集合方法,如下是經常使用的集合方法說明:
add:把指定元素添加到集合。
remove:從集合中刪除指定元素。
contains:判斷集合是否包含指定元素。
clear:清空集合。
isEmpty:判斷集合是否爲空。
size:獲取集合的大小(即所包含元素的個數)。
從以上說明可見,這些集合方法仍是蠻基礎的,不但基礎並且通用,不光是集合會用到這些方法,連映射和清單也要用到它們,於是後面在介紹映射和清單之時,就再也不重複說明上述的基本方法了。
接着來個集合的初步運用,功能很簡單,僅僅往字符串集合添加五個字符串,而後獲取並打印該集合的大小,示例代碼以下:程序員
HashSet<String> set = new HashSet<String>(); set.add("hello"); set.add("world"); set.add("how"); set.add("are"); set.add("you"); System.out.println("set.size()=" + set.size());
運行上面的測試代碼,可得日誌結果爲「set.size()=5」。不過這裏只得到集合大小,若想知曉集合內部到底有哪些字符串,還得依次遍歷該集合的全部元素才行。集合元素的遍歷方式主要有三種:for循環遍歷、迭代器遍歷、forEach遍歷,接下來分別進行介紹。數據庫
一、for循環遍歷
這個for循環屬於簡化的for循環,早在遍歷數組元素的時候,你們已經見識過了。廢話沒必要多說,直接看下列代碼好了:編程
// 第一種遍歷方式:簡化的for循環一樣適用於數組和容器 for (String hash_item : set) { System.out.println("hash_item=" + hash_item); }
運行以上的循環代碼,輸出了以下的日誌信息,可見該集合的五個元素全都找到了:數組
hash_item=how hash_item=world hash_item=are hash_item=hello hash_item=you
二、迭代器遍歷
迭代器又稱指示器,其做用相似於數據庫的遊標,以及C語言的指針。調用集合實例的iterator方法便可得到該集合的迭代器,初始的迭代器指向集合的存儲地址;它的hasNext方法用來判斷後方是否存在集合元素,假若不存在則表示到末尾了;迭代器另有next方法用於獲取下一個元素,同時迭代器移動到下一個地址。因而屢次調用集合實例的next方法,便可逐次取出該集合的每一個元素。下面是利用迭代器遍歷集合的代碼例子:ide
// 第二種遍歷方式:利用迭代器循環遍歷集合。 Iterator<String> iterator = set.iterator(); while (iterator.hasNext()) { // 迭代器後方是否存在元素 // 獲取迭代器後方的元素 String hash_iterator = (String) iterator.next(); System.out.println("hash_iterator=" + hash_iterator); }
三、forEach遍歷
forEach是Java8新增的容器遍歷方法,一樣適用於映射和清單。它藉助Lambda表達式可以完成最簡化的遍歷操做,僅僅一行代碼就搞定了集合元素的循環輸出功能,具體實現代碼以下所示:測試
// 第三種遍歷方式:使用forEach方法夾帶Lambda表達式進行遍歷 set.forEach(hash_each -> System.out.println("hash_each=" + hash_each));
講完了集合的三種遍歷方式,按說集合的常見用法均涉及到了,那麼爲啥集合還要分紅哈希集合與二叉集合兩類呢?這緣於集合的基本特性規定了集合裏的每一個元素是惟一的,但並未規定集合裏的元素須要按照順序排列。從前面哈希集合的遍歷結果可知,哈希集合裏面保存的各元素是無序的,由於一個數據的哈希結果是散列值,天南地北處處跑,天然沒法按照元素值進行排序。二叉集合的設計正是要解決這個順序問題,因爲二叉集合內部採起二叉樹存儲數據,每一個新加入的元素都要與原住民們比較一番,好決定這個新元素是放在某個原住民的左節點仍是右節點;所以,假若把一組字符串前後加入二叉集合,那麼每次新增元素的操做都會進行大小比較,最終獲得的二叉集合一定是有序的。this
爲了驗證二叉集合的添加操做是否符合設計原理,接下來不妨建立一個二叉集合的實例,再往其中添加多個字符串,而後遍歷打印該字符串集合的全部元素。據此從新編寫後的二叉集合演示代碼以下所示:編碼
TreeSet<String> set = new TreeSet<String>(); set.add("hello"); set.add("world"); set.add("how"); set.add("are"); set.add("you"); // 第一種遍歷方式:簡化的for循環一樣適用於數組和容器 for (String tree_item : set) { System.out.println("tree_item=" + tree_item); }
運行上述的演示代碼,觀察如下的日誌信息可知,這個二叉集合的遍歷結果爲按照字符串首字母升序排列:
tree_item=are tree_item=hello tree_item=how tree_item=world tree_item=you
須要注意的是,無論是哈希值計算,仍是二叉節點比較,都須要元素歸屬的數據類型提供計算方法或者比較方法。對於包裝類型、字符串等系統自帶的數據類型來講,Java已經在它們的源碼中實現了相關方法,因此這些數據類型容許程序員在集合中直接使用。然而若是是開發者本身定義的數據類型(新的類),就要求開發者本身來實現計算方法和比較方法了。
譬若有個自定義的手機類MobilePhone,該類的定義代碼見下:
//定義一個手機類 public class MobilePhone { private String brand; // 手機品牌 private Integer price; // 手機價格 public MobilePhone(String brand, int price) { this.brand = brand; this.price = price; } // 獲取手機品牌 public String getBrand() { return this.brand; } // 獲取手機價格 public int getPrice() { return this.price; } }
如今給手機類分別建立對應的哈希集合與二叉集合,並對兩種集合分別添加若干手機實例,結果會發現,手機的哈希集合竟然會插入品牌與價格重複的元素!同時手機的二叉集合也變成亂序的了,由於編譯器不曉得究竟要按照品牌排序仍是按照價格排序。既然編譯器無從判斷待添加的元素是否重複,也沒法判斷新添加的元素根據哪一個字段排序,程序員就得在手機類的定義代碼中指定相關的判斷規則了。
就哈希集合的哈希值計算而言,自定義的手機類須要重寫hashCode和equals方法,其中hashCode方法計算獲得的哈希值對應於該對象的保存位置,而equals方法用來判斷該位置上的幾個元素是否徹底相等。一方面,咱們要保證品牌與價格都相同的兩個元素,它們的哈希值必須也相等;另外一方面,即便兩個元素的品牌和價格不一致,它們的哈希值也可能恰巧相等,因而還須要equals方法進一步校驗是否存在重複。按照上述要求,重寫後的hashCode和equals方法代碼示例以下:
// hashCode方法計算出來的哈希值對應於該對象的保存位置 @Override public int hashCode() { return brand.hashCode() + price.hashCode(); } // 同一個存儲位置上可能有多個對象(哈希值剛好相等), // 此時系統自動調用equals方法判斷是否存在相同的對象。 @Override public boolean equals(Object obj) { if (!(obj instanceof MobilePhoneHash)) { return false; } MobilePhoneHash other = (MobilePhoneHash) obj; // 手機品牌和手機價格都相等,纔算是這兩個手機相等 boolean equals = this.brand.equals(other.brand) && this.price.equals(other.price); return equals; }
至於二叉集合的節點大小比較,則需手機類實現接口Comparable,並具體定義該接口聲明的compareTo方法(該方法用來比較兩個元素的大小關係)。其實這裏的Comparable接口與數組排序用到的Comparator接口做用相似,都是判斷兩個對象誰大誰小。若是要求二叉集合裏面的手機元素按照價格排序,則compareTo方法主要校驗當前手機的價格與其它手機的價格。詳細的接口實現代碼以下所示:
public class MobilePhoneTree implements Comparable<MobilePhoneTree> { // 此處省略手機類的構造方法、成員屬性與成員方法定義 // 二叉樹除了檢查是否相等,還要判斷前後順序。 // 相等和前後順序的校驗結果從compareTo方法得到。 @Override public int compareTo(MobilePhoneTree other) { if (this.price.compareTo(other.price) > 0) { // 當前價格較高 return 1; } else if (this.price.compareTo(other.price) < 0) { // 當前價格較低 return -1; } else { return this.brand.compareTo(other.brand); } } }
通過一番折騰以後,再對新定義的兩個手機類分別對哈希集合與二叉集合開展驗證,結果應當爲:哈希集合不會插入重複的手機對象,而且二叉集合裏的各手機元素按照價格升序排列。
更多Java技術文章參見《Java開發筆記(序)章節目錄》