前言:俗話說(shuō)“金三銀四銅五”,不知道我要在這段時(shí)間找工作會(huì)不會(huì)很艱難。不管了,工作三年之后就當(dāng)給自己放個(gè)暑假。

 

面試當(dāng)中Collection(集合)是基礎(chǔ)重點(diǎn).我在網(wǎng)上看了幾篇講Collection的文章,大多都是以羅列記憶點(diǎn)的形式書(shū)寫(xiě)的,沒(méi)有談?wù)搶?shí)現(xiàn)細(xì)節(jié)和邏輯原理。作為個(gè)人筆記無(wú)可厚非,但是并不利于他人學(xué)習(xí)。希望能通過(guò)這種比較“費(fèi)勁”的講解,幫助我自己、也幫助讀者們更好地學(xué)習(xí)Java、掌握J(rèn)ava.

 

無(wú)論你跟我一樣需要應(yīng)聘,還是說(shuō)在校學(xué)生學(xué)習(xí)Java基礎(chǔ),都對(duì)入門和進(jìn)一步啟發(fā)學(xué)習(xí)有所幫助。(關(guān)于Collection已經(jīng)寫(xiě)過(guò)一篇文章,可以在本文最后點(diǎn)擊鏈接閱讀)。

 

1.3 拒絕重復(fù)內(nèi)容的Set

Set,跟數(shù)學(xué)中的概念“集合”是一樣的,就是沒(méi)有重復(fù)的元素。在JavaSE的Cellection框架里,Set是三大陣營(yíng)之一。根據(jù)“核心框架圖”,我們可以看到它的位置。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

同樣一張訂單,已經(jīng)支付過(guò)一次就不能再次支付,否則就是重復(fù)支付。反映在系統(tǒng)當(dāng)中,就是收集對(duì)象時(shí),如果有相同的對(duì)象,則不再重復(fù)收集。如果有這類需求,我們可以用使用實(shí)現(xiàn)Set接口的類。之前講過(guò)的List和之后會(huì)談到的Queue,都對(duì)是否重復(fù)沒(méi)有要求,這是Set的特性。

 

1.3.1 如何使用HashSet

除非已經(jīng)是大小牛級(jí)別的人能做到觸類旁通,否則最好在學(xué)習(xí)API的時(shí)候做幾個(gè)簡(jiǎn)單的實(shí)驗(yàn),不僅可以更實(shí)際地幫助理解,還可以加深印象有助于長(zhǎng)期記憶。

 

輸入一段英文,經(jīng)過(guò)處理后需要輸出所有不重復(fù)的單詞。HashSet實(shí)現(xiàn)了Set接口,我們不妨就用它來(lái)寫(xiě)一段demo。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 1 import java.util.*; 2  3 /** 4  * HashSet的實(shí)驗(yàn)用例 5  */ 6 public class Words { 7     public static void main(String[] args) { 8         Scanner scanner = new Scanner(System.in); 9         System.out.print("請(qǐng)輸入一段話:");10         String line = scanner.nextLine();11         String[] tokens = line.split(" ");//根據(jù)空格劃分單詞12         Set words = new HashSet();13         for(String token : tokens) {14             words.add(token);//使用HashSet收集單詞15         }16         System.out.printf("不重復(fù)的單詞有:%d 個(gè): %s%n", words.size(), words);17     }18 }

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

英語(yǔ)中分詞沒(méi)有中文分詞那么困難,基本上可以按照空格來(lái)劃分單詞。很明顯,輸出的結(jié)果是正確的。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

這時(shí)候不知道你是否也有這么一個(gè)疑問(wèn):HashSet是如何判斷哪些單詞重復(fù)的呢?如果讓你來(lái)做,你會(huì)怎么做?

 

1.3.2 Java中判斷重復(fù)對(duì)象的規(guī)范

如果對(duì)象是字符串,我們可以采用逐一比較的方式,比較即將收集的字符串和已有的字符串是否相同;如果對(duì)象是數(shù)值,那就更簡(jiǎn)單了??墒牵酥獾膶?duì)象怎么辦?我們先來(lái)看一個(gè)沒(méi)那么復(fù)雜的例子。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 1 import java.util.*; 2  3 /** 4  * Set測(cè)試用例 5  */ 6     class Student { 7     private String name; 8     private String number; 9 10     Student(String name, String number) {11         this.name = name;12         this.number = number;13     }14 15 16     @Override17     public String toString() {18         return String.format("(%s, %s)", name, number);19     }20 }21 22     public class Students {23         public static void main(String[] args) {24             Set set = new HashSet();25             set.add(new Student("Tom", "001"));26             set.add(new Student("Sam", "002"));27             set.add(new Student("Tom", "001"));28             System.out.println(set);29         }30     }

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

上面這段demo是在模擬一個(gè)學(xué)生注冊(cè)系統(tǒng),錄入姓名和學(xué)號(hào),最后輸出已經(jīng)注冊(cè)了的學(xué)生。由于同一個(gè)學(xué)生不能注冊(cè)兩次,所以使用了HashSet來(lái)收集對(duì)象。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

這樣的輸出結(jié)果是否出乎你的意料?顯然,在執(zhí)行過(guò)程中Set并沒(méi)有把重復(fù)的學(xué)生數(shù)據(jù)排除。其實(shí)是我們太一廂情愿了,因?yàn)樵趯?xiě)程序的時(shí)候并沒(méi)有告訴Set,什么樣的Student實(shí)例才算是重復(fù)。要判斷對(duì)象是否重復(fù),必須實(shí)現(xiàn)hashCode()和equals()方法。在之前的英文分詞例子中,對(duì)象是String,我們可以在源代碼中看到它已經(jīng)實(shí)現(xiàn)了這兩個(gè)方法。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 1 /** 2      * Compares this string to the specified object.  The result is {@code 3      * true} if and only if the argument is not {@code null} and is a {@code 4      * String} object that represents the same sequence of characters as this 5      * object. 6      * 7      * @param  anObject 8      *         The object to compare this {@code String} against 9      *10      * @return  {@code true} if the given object represents a {@code String}11      *          equivalent to this string, {@code false} otherwise12      *13      * @see  #compareTo(String)14      * @see  #equalsIgnoreCase(String)15      */16     public boolean equals(Object anObject) {17         if (this == anObject) {18             return true;19         }20         if (anObject instanceof String) {21             String anotherString = (String)anObject;22             int n = value.length;23             if (n == anotherString.value.length) {24                 char v1[] = value;25                 char v2[] = anotherString.value;26                 int i = 0;27                 while (n-- != 0) {28                     if (v1[i] != v2[i])29                         return false;30                     i++;31                 }32                 return true;33             }34         }35         return false;36     }37 38 /**39      * Returns a hash code for this string. The hash code for a40      * {@code String} object is computed as41      * <blockquote><pre>42      * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]43      * </pre></blockquote>44      * using {@code int} arithmetic, where {@code s[i]} is the45      * <i>i</i>th character of the string, {@code n} is the length of46      * the string, and {@code ^} indicates exponentiation.47      * (The hash value of the empty string is zero.)48      *49      * @return  a hash code value for this object.50      */51     public int hashCode() {52         int h = hash;53         if (h == 0 && value.length > 0) {54             char val[] = value;55 56             for (int i = 0; i < value.length; i++) {57                 h = 31 * h + val[i];58             }59             hash = h;60         }61         return h;62     }

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

事實(shí)上不只有HashSet,Java中許多要判斷對(duì)象是否重復(fù)時(shí),都要求調(diào)用hashCode()與equals()方法,因此官方規(guī)范中建議這兩個(gè)方法必須同時(shí)實(shí)現(xiàn)。如果我們?cè)谥皩W(xué)生注冊(cè)的例子中添加hashCode()與equals()方法的實(shí)現(xiàn),重復(fù)的數(shù)據(jù)就不會(huì)出現(xiàn)。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 1 import java.util.*; 2  3 /** 4  * Set測(cè)試用例 5  */ 6     class Student { 7     private String name; 8     private String number; 9 10     Student(String name, String number) {11         this.name = name;12         this.number = number;13     }14 15     /**16      * 重載equals()和hashcode()17      */18     @Override19     public boolean equals(Object obj) {20         if(obj == null) {21             return false;22         }23         if(getClass() != obj.getClass()) {24             return false;25         }26         final Student other = (Student) obj;27         return true;28     }29 30     @Override31     public int hashCode() {32         int hash = 5;33         hash = 13 * hash + (this.name != null ? this.name.hashCode() : 0);34         hash = 13 * hash + (this.number != null ? this.number.hashCode() : 0);35         return hash;36     }37 38 39     @Override40     public String toString() {41         return String.format("(%s, %s)", name, number);42     }43 }44 45     public class Students {46         public static void main(String[] args) {47             Set set = new HashSet();48             set.add(new Student("Tom", "001"));49             set.add(new Student("Sam", "002"));50             set.add(new Student("Tom", "001"));51             System.out.println(set);52         }53     }

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

重載的hashCode()和equals()方法定義了“如果學(xué)生的姓名與學(xué)號(hào)相同,那就是重復(fù)的對(duì)象”。當(dāng)然,你也可以根據(jù)自己的理解,改寫(xiě)成“如果學(xué)號(hào)相同,即為重復(fù)”。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

1.3.3 Set小結(jié)

Set收集對(duì)象時(shí),如果發(fā)現(xiàn)有重復(fù)的數(shù)據(jù),會(huì)不再收集該對(duì)象。如果要實(shí)現(xiàn)這一點(diǎn),必須告知符合什么樣的條件才算是“重復(fù)”。

 

Java規(guī)范中通過(guò)重載hashCode()和equals()方法來(lái)判斷是否重復(fù)。如果你要收集的對(duì)象不屬于String或Integet之類API已經(jīng)提供好的類,務(wù)必要記得實(shí)現(xiàn)這兩個(gè)方法。

 

通過(guò)學(xué)習(xí)Set和閱讀源代碼,不僅可能更好地掌握常用API的用法,同時(shí)也會(huì)Java規(guī)范有了意料之外情理之中的深入了解。無(wú)論是對(duì)Java的學(xué)習(xí),還是日常的開(kāi)發(fā)維護(hù)工作,都有不小的幫助。 

 

1.4 支持隊(duì)列操作的Queue

什么是隊(duì)列?它是最常用的數(shù)據(jù)結(jié)構(gòu)之一,只允許在隊(duì)列的前端(front)進(jìn)行刪除操作,而在表的后端(rear)進(jìn)行插入操作。

 

顧名思義,只要是需要“排隊(duì)”的應(yīng)用場(chǎng)景,都可以考慮使用隊(duì)列,例如餐廳的排隊(duì)系統(tǒng),醫(yī)院的器官輪候系統(tǒng)等。

 

1.4.1 Queue的實(shí)現(xiàn)規(guī)范

在介紹完List、Set之后,我們來(lái)看看Collection的最后一大塊Queue. 

 

Queue定義了自己特有的offer()、poll()和peek()等方法。建議優(yōu)先使用offer()方法,而不是add()方法來(lái)收集對(duì)象。同樣地,pool()和peek()方法建議優(yōu)先于remove()、element()方法使用。他們最主要的區(qū)別在于,add()、remove()、element()方法出錯(cuò)時(shí)會(huì)拋出異常,offer()、poll()、peek()方法則會(huì)返回特定值。

 

前一篇介紹List的文章就介紹過(guò)LinkedList。從反復(fù)提及的核心架構(gòu)圖中可以看出,其實(shí)它不僅實(shí)現(xiàn)了List,同時(shí)也是一種Queue。我們不妨就用LinkedList來(lái)寫(xiě)一段demo,試著使用隊(duì)列。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 1 import java.util.*; 2  3 /** 4  * Queue實(shí)驗(yàn)用例 5  */ 6 interface Request { 7     void execute(); 8 } 9 10 public class RequestQueue {11 12     public static void main(String[] args) {13         Queue requests = new LinkedList();14         // 模擬將請(qǐng)求加入隊(duì)列15         for (int i = 1; i < 6; i++) {16             requests.offer(new Request() {17                public void execute() {18                    System.out.printf("處理數(shù)據(jù) %f%n", Math.random());19                }20             });21         }22         process(requests);23     }24 25     // 處理隊(duì)列中的請(qǐng)求26     private static void process(Queue requests) {27         while(requests.peek() != null) {28             Request request = (Request) requests.poll();29             request.execute();30         }31     }32 }

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

由于是隨機(jī)產(chǎn)生的數(shù)字,所以幾乎每一次實(shí)驗(yàn)結(jié)果都會(huì)不一樣,不過(guò)這并不重要。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

1.4.2 既是隊(duì)列又是棧的Deque

有的時(shí)候,我們會(huì)想對(duì)隊(duì)列的前端與尾端進(jìn)行操作,能在前端加入對(duì)象、取出對(duì)象,也能在尾端加入對(duì)象和取出對(duì)象。Queue的子接口Deque可以滿足這個(gè)需求,我們?cè)诤诵目蚣軋D上可以很容易找到它的位置。

 

Queue的行為和Deque的行為有所重復(fù),有幾個(gè)方法是等義的,例如前者的add()等于后者的addLast()方法,建議感興趣的朋友自行查看源代碼或API說(shuō)明文檔。

 

java.util.ArrayDeque實(shí)現(xiàn)了Deque接口,我們可以通過(guò)寫(xiě)一段操作容量有限的堆棧的demo來(lái)看看如何使用它。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 1 import java.util.*; 2  3 /** 4  * Deque實(shí)驗(yàn)用例 5  */ 6 public class Stack { 7     private Deque deque = new ArrayDeque(); 8     private int capacity; 9 10     public Stack(int capacity) {11         this.capacity = capacity;12     }13 14     public boolean push(Object o) {15         if(deque.size() + 1 > capacity) {16             return false;17         }18         return deque.offerLast(o);19     }20 21     public Object pop() {22         return deque.pollLast();23     }24 25     public Object peek() {26         return deque.peekLast();27     }28 29     public int size() {30         return deque.size();31     }32 33     public static void main(String[] args) {34         Stack stack = new Stack(5);35         stack.push("小明");36         stack.push("小花");37         stack.push("小光");38         System.out.println(stack.pop());39         System.out.println(stack.pop());40         System.out.println(stack.pop());41     }42 }

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

堆棧結(jié)構(gòu)的特性是先進(jìn)后出,所以運(yùn)行結(jié)果是先顯示小光,最后顯示小明。

iOS培訓(xùn),Swift培訓(xùn),蘋果開(kāi)發(fā)培訓(xùn),移動(dòng)開(kāi)發(fā)培訓(xùn)

 

一道思考題:從核心框架圖中可以看出LinkedList也實(shí)現(xiàn)了Deque接口,不過(guò)在這個(gè)demo里面,使用ArrayDeque速度上要比LinkedList快。這是為什么?

 

1.4.3 Quque小結(jié)

隊(duì)列是一種常見(jiàn)而且重要的數(shù)據(jù)結(jié)構(gòu),JavaSE中Collection的三大分支之一Quque提供了相應(yīng)的實(shí)現(xiàn)。

 

Deque是一種雙向隊(duì)列,同時(shí)也是Queue的一個(gè)子接口。它們之間既有等義的方法,也有不同的實(shí)現(xiàn),具體情況需要閱讀API說(shuō)明文檔或直接查看源代碼。

 

我們?cè)趯W(xué)習(xí)之前,不妨可以先試著用Java基本語(yǔ)法實(shí)現(xiàn)隊(duì)列、堆棧等數(shù)據(jù)結(jié)構(gòu)和標(biāo)準(zhǔn)操作方法。在此基礎(chǔ)上再閱讀相應(yīng)的源代碼(例如LinkedList),會(huì)格外發(fā)現(xiàn)代碼的邏輯之美和整潔之美。

 

 

 相關(guān)文章推薦:

JavaSE中Collection集合框架學(xué)習(xí)筆記(1)——具有索引的List

 

如果你喜歡我的文章,可以掃描關(guān)注我的個(gè)人公眾號(hào)“李文業(yè)的思考筆記”。

http://www.cnblogs.com/levenyes/p/7120267.html