前言:暑期應該開始了,因為小區對面的小學這兩天早上都沒有像以往那樣一到七八點鍾就人聲喧鬧、車水馬龍。
前兩篇文章介紹了Collection框架的主要接口和常用類,例如List、Set、Queue,和ArrayList、HashSet、LinkedList等等。根據核心框架圖,相信我們都已經對Collection這個JavaSE中最常用API之一有一個較為全面的認識。
這個學習過程,還可以推及到其他常用開源框架和公司項目的學習和熟悉上面。借助開發工具或說明文檔,先是對項目整體有一個宏觀的認識,再根據這個認識逐一熟悉各個核心模塊。(如果對關於Collection的兩篇文章感興趣的話,可以在文章的最末尾點擊鏈接閱讀。)
1.5 遍歷對象的Iterator
Collection,顧名思義,就是收集,在JavaSE當中起到了收集對象的作用。
收集完對象之后,有一個非常普遍的需求,就是要遍歷所收集的對象。學生報名之后,老師有瀏覽都有哪些學生已經注冊的需要;客戶下了訂單之后, 商家有看看哪些商品被購買的需要;病人掛號之后,醫院工作人員有了解門診工作量的需要。
1.5.1 遍歷對象遇到的問題
如果要寫一個forEach()方法,可以顯示List收集的所有對象,也許你會這么寫:
1 private static void forEach(List list) { 2 int size = list.size(); 3 for(int i = 0; i < size; i++) { 4 System.out.println(list.get(i)); 5 } 6 }
這個方法適用於所有實現List接口的對象,如ArrayList、LinkedList等。如果要讓你寫個forEach()方法顯示Set收集的所有對象,你該怎么寫呢?在查看過Set的API說明文檔后,發現有個toArray()方法,可以將Set收集的對象轉為Object[]返回,所以你會這么寫:
1 private static void forEach(Set set) { 2 for(Object o : set.toArray()) { 3 System.out.println(o); 4 } 5 }
這個方法適用於所有操作Set接口的對象,如HashSet、TreeSet等。如果現在要讓你再寫一個forEach()方法,可以顯示Queue收集的對象,也許你會這么寫:
1 private static void forEach(Queue queue) { 2 while(queue.peek() != null) { 3 System.out.println(queue.poll()); 4 } 5 }
表面上看來好像是正確的,不過Queue的poll()方法會取出對象,當你顯示完Queue中所有對象,Queue也空了。這並不是我們想要的結果,怎么辦呢?
1.5.2 使用Iterator
事實上,無論是List、Set還是Queue,都會有個Queue,都會有個iterator()方法,這個方法在JDK1.4之前,是定義在Collection接口中,而它們都繼承自Collection,所以也都擁有iterator()的行為。
iterator()方法會返回java.util.Iterator接口的操作對象,這個對象包括了Collection收集的所有對象,你可以使用Iterator的hasNext()看看有無下一個對象,若有的話,再使用next()取得下一個對象。因此,無論List、Set、Queue還是任何Collection,都可以使用以下的forEach()方法來顯示所收集的對象:
1 private static void forEach(Collection collection) { 2 Iterator iterator = collection.iterator(); 3 while(iterator.hasNext()) { 4 System.out.println(iterator.next()); 5 } 6 }
在JDK5之后,原先定義在Collection中的iterator()方法,提升至新的java.util.Iterable父接口,因此在JDK5之后,可以使用以下forEach()方法顯示收集的所有對象:
1 private static void forEach(Iterable iterable) { 2 Iterator iterator = iterable.iterator(); 3 while(iterator.hasNext()) { 4 System.out.println(iterator.next()); 5 } 6 }
接下來,我們可以寫一個比較完整的demo,看看是否能正確使用forEach()方法:
1 import java.util.*; 2
3 /**
4 * Iterator實驗用例 5 */
6 public class ForEach { 7 private static void forEach(Iterable iterable) { 8 Iterator iterator = iterable.iterator(); 9 while(iterator.hasNext()) { 10 System.out.println(iterator.next()); 11 } 12 } 13
14 public static void main(String[] args) { 15 List list = Arrays.asList("Tim", "Jack", "Jane"); 16 forEach(list); 17 forEach(new HashSet(list)); 18 forEach(new ArrayDeque(list)); 19 } 20 }
運行之后,我們可以得到以下結果:
1.5.3 Iterator小結
遍歷顯示所收集的所有對象,這是使用Celletion框架時頻繁會遇到的需求。在了解List、Set、Queue如何顯示收集對象之后,我們意外地發現可以通過使用定義在上層接口的iterator()方法,達到正確、安全地滿足這一需求的目的。
無論是學習Colletion等JavaSE的API框架,還是熟悉開源或公司項目,在熟悉各個模塊的同時,也要有抽象的理解能力來宏觀地理解項目。在滿足一些普遍常用需求時,可以找到更通用、復用程度更高的解決方案。如果能做到這一點,就不會犯新手常犯的“只見樹木不見森林”的錯誤。
1.6 收集對象后的排序
在收集對象之后,對對象進行排序是常用的動作。
我的上家是做ERP系統的,常常有這樣的需求:幾家供應商提供的報價單價格不一樣,系統用戶希望可以在按照報價從低到高查看商品的報價;生產物料緊缺時,他們希望根據物料需求日期從近到遠查看;同一個生產件,不同的生產配方和工藝流程產生的生產成本是不一樣的,他們希望能按照成本從低到高查看生產明細。以上的這些需求,都要求系統對所收集的不同對象進行排序
1.6.1 Collection自帶的排序算法
java.util.Collections提供有sort()方法,用來滿足對對象進行排序的需求。由於必須有索引才能進行排序,因此Collections的sort()方法接受List實現對象。例如以下這段demo:
1 import java.util.*; 2
3 /**
4 * Collections的sort()方法實驗用例 5 */
6 public class Sort { 7 public static void main(String[] args) { 8 List numbers = Arrays.asList(10, 3, 4, 21, 9); 9 Collections.sort(numbers); 10 System.out.println(numbers); 11 } 12 }
執行結果我們可以看到已經排好序的一串數字:
可是,如果我們需要排序的對象稍微復雜一點點,會出現什么樣的情況呢?
1 import java.util.*; 2
3 /**
4 * Collections的sort()方法實驗用例2 5 */
6 class Account { 7 private String name; 8 private int balance; 9
10 Account (String name, int balance) { 11 this.name = name; 12 this.balance = balance; 13 } 14
15 @Override 16 public String toString() { 17 return String.format("Account(%s, %d)", name, balance); 18 } 19 } 20
21 public class Sort { 22 public static void main(String[] args) { 23 List accounts = Arrays.asList( 24 new Account("Tim", 100), 25 new Account("Tom", 1300), 26 new Account("Jack", 5) 27 ); 28 Collections.sort(accounts); 29 System.out.println(accounts); 30 } 31 }
運行結果出現了拋出ClassCastException報錯,到底是怎么回事呢?
1.6.2 實現Comparable
要說原因,是因為你根本沒告訴Collections的sort()方法,到底要根據Account的name還是balance進行排序。用一句時下流行的話說:“我有什么辦法,我也很絕望啊。”
Collections的sort()方法要求被排序的對象必須實現java.lang.Comparable接口,這個接口有個compareTo()方法必須返回大於0、等於0或小於0的數。這有什么用呢?我們直接來看下面這個針對賬戶余額排序的demo就了解了:
1 import java.util.*; 2
3 /**
4 * Collections的sort()方法實驗用例3 5 */
6 class Account implements Comparable{ 7 private String name; 8 private int balance; 9
10 Account (String name, int balance) { 11 this.name = name; 12 this.balance = balance; 13 } 14
15 @Override 16 public String toString() { 17 return String.format("Account(%s, %d)", name, balance); 18 } 19
20 @Override 21 public int compareTo(Object o) { 22 Account other = (Account) o; 23 return this.balance - other.balance; 24 } 25 } 26
27 public class Sort { 28 public static void main(String[] args) { 29 List accounts = Arrays.asList( 30 new Account("Tim", 100), 31 new Account("Tom", 1300), 32 new Account("Jack", 5) 33 ); 34 Collections.sort(accounts); 35 System.out.println(accounts); 36 } 37 }
Collections的sort()方法在取得a對象與b對象進行比較時,會先將對象Cast為Comparable(也因為這樣,如果對象沒實現這個接口,就會拋出ClassCastException),然后調用a.compareTo(b),如果a對象順序上小於b對象,必須返回小於0的值;如果順序上相等則返回0;如果順序上a大於b,則要返回大於0的值。因此,上面的demo輸出結果會是按照余額從小到大排列:
1.6.3 實現Comparator
如果你有用過Collections對所收集的String對象排序,你應該會知道JavaSE是按照A、B、C的字母表來排序的。可是,如果今天突然有個需求,要讓排序結果反過來呢?
首先,String已經實現了Comparable接口,我們很難進行修改。另外,由於String聲明為final,我們也沒有辦法通過繼承的方式重新定義compareTo()方法。不過幸好,JavaSE給這種情況留下了備用的解決方案。
Collections的sort()方法有另一個重載版本,可以接受java.util.Comparator接口的操作對象。如果使用這個版本,排序方式將根據Comparator的compare()方法的定義來決定。例如下面這個demo:
1 import java.util.*; 2
3 /**
4 * Collections的sort()方法實驗用例4 5 */
6 class StringComparator implements Comparator { 7 @Override 8 public int compare(Object o1, Object o2) { 9 String str1 = (String) o1; 10 String str2 = (String) o2; 11 return -str1.compareTo(str2); 12 } 13 } 14
15 public class Sort { 16 public static void main(String[] args) { 17 List words = Arrays.asList("B", "C", "A", "X", "Z", "Y"); 18 Collections.sort(words, new StringComparator()); 19 System.out.println(words); 20 } 21 }
結果如下,符合我們之前的期望:
1.6.4 Sort()小結
在Java的規范中,與順序有關的行為,通常要不就是對象本身是Comparable,即實現了Comparable接口,要不就是另行制定Comparator對象告知如何排序。
這就是深入學習API的好處,可以介紹遇到麻煩的次數。另外,無論你的工作語言是Java還是PHP、C#,熟悉語言規范是必不可少的內功,是衡量一個程序員實力的硬指標。
相關文章推薦:
JavaSE中Collection集合框架學習筆記(1)——具有索引的List
JavaSE中Collection集合框架學習筆記(2)——拒絕重復內容的Set和支持隊列操作的Queue
如果你喜歡我的文章,可以掃描關注我的個人公眾號“李文業的思考筆記”。
不定期地會推送我的原創思考文章。