原文:http://www.ibm.com/developerworks/cn/java/j-5things2.html
關於 Java Collections API 您不知道的 5 件事,第 1 部分
定製和擴展 Java Collections
簡介: Java™ Collections API 遠不止是數組的替代品,雖然一開始這樣用也不錯。Ted Neward 提供了關於用 Collections 做更多事情的 5 個技巧,包括關於定製和擴展 Java Collections API 的基礎。
發佈日期:
2010 年 5 月 24 日
級別:
初級
其他語言版本:
英文
對於很多 Java 開發人員來說,Java Collections API 是標準 Java 數組及其所有缺點的一個非常需要的替代品。將
Collections 主要與 ArrayList
聯繫到一起本身沒有錯,但是對於那些有探索精神的人來說,這只是
Collections 的冰山一角。
雖然 Map
(以及它的常用實現
HashMap
)非常適合名-值對或鍵-值對,但是沒有理由讓自己侷限於這些熟悉的工具。可以使用適當的
API,甚至適當的 Collection 來修正很多易錯的代碼。
本文是
5
件事
系列
中的第二篇文章,也是專門討論 Collections 的 7 篇文章中的第一篇文章,之所以花這麼大的篇幅討論
Collections,是因爲這些集合在 Java 編程中是如此重要。首先我將討論做每件事的最快(但也許不是最常見)的方式,例如將 Array
中的內容轉移到 List
。然後我們深入探討一些較少人知道的東西,例如編寫定製的 Collections 類和擴展
Java Collections API。
剛接觸 Java 技術的開發人員可能不知道,Java 語言最初包括數組,是爲了應對上世紀 90 年代初期 C++ 開發人員對於性能方面的批評。從那時到現在,我們已經走過一段很長的路,如今,與 Java Collections 庫相比,數組不再有性能優勢。
例如,若要將數組的內容轉儲到一個字符串,需要迭代整個數組,然後將內容連接成一個 String
;而
Collections 的實現都有一個可用的 toString()
實現。
除少數情況外,好的做法是儘快將遇到的任何數組轉換成集合。於是問題來了,完成這種轉換的最容易的方式是什麼?事實證明,Java Collections API 使這種轉換變得容易,如清單 1 所示:
import java.util.*; public class ArrayToList { public static void main(String[] args) { // This gives us nothing good System.out.println(args); // Convert args to a List of String List<String> argList = Arrays.asList(args); // Print them out System.out.println(argList); } } |
注意,返回的 List
是不可修改的,所以如果嘗試向其中添加新元素將拋出一個 UnsupportedOperationException
。
而且,由於 Arrays.asList()
使用 varargs
參數表示添加到 List
的元素,所以還可以使用它輕鬆地用以 new
新建的對象創建
List
。
將一個集合(特別是由數組轉化而成的集合)的內容轉移到另一個集合,或者從一個較大對象集合中移除一個較小對象集合,這些事情並不鮮見。
您也許很想對集合進行迭代,然後添加元素或移除找到的元素,但是不要這樣做。
在此情況下,迭代有很大的缺點:
- 每次添加或移除元素後重新調整集合將非常低效。
- 每次在獲取鎖、執行操作和釋放鎖的過程中,都存在潛在的併發困境。
- 當添加或移除元素時,存取集合的其他線程會引起競爭條件。
可以通過使用 addAll
或 removeAll
,傳入包含要對其添加或移除元素
的集合作爲參數,來避免所有這些問題。
Java 5 中加入 Java 語言的最大的便利功能之一,增強的 for 循環,消除了使用 Java 集合的最後一道障礙。
以前,開發人員必須手動獲得一個 Iterator
,使用 next()
獲得 Iterator
指向的對象,並通過 hasNext()
檢查是否還有更多可用對象。從 Java 5 開始,我們可以隨意使用 for
循環的變種,它可以在幕後處理上述所有工作。
實際上,這個增強適用於實現 Iterable
接口的任何對象
,而不僅僅是 Collections
。
清單 2 顯示通過 Iterator
提供 Person
對象的孩子列表的一種方法。
這裏不是提供內部 List
的一個引用
(這使 Person
外的調用者可以爲家庭增加孩子 — 而大多數父母並不希望如此),Person
類型實現 Iterable
。這種方法還使得 for 循環可以遍歷所有孩子。
// Person.java import java.util.*; public class Person implements Iterable<Person> { public Person(String fn, String ln, int a, Person... kids) { this.firstName = fn; this.lastName = ln; this.age = a; for (Person child : kids) children.add(child); } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public int getAge() { return this.age; } public Iterator<Person> iterator() { return children.iterator(); } public void setFirstName(String value) { this.firstName = value; } public void setLastName(String value) { this.lastName = value; } public void setAge(int value) { this.age = value; } public String toString() { return "[Person: " + "firstName=" + firstName + " " + "lastName=" + lastName + " " + "age=" + age + "]"; } private String firstName; private String lastName; private int age; private List<Person> children = new ArrayList<Person>(); } // App.java public class App { public static void main(String[] args) { Person ted = new Person("Ted", "Neward", 39, new Person("Michael", "Neward", 16), new Person("Matthew", "Neward", 10)); // Iterate over the kids for (Person kid : ted) { System.out.println(kid.getFirstName()); } } } |
在域建模的時候,使用 Iterable
有一些明顯的缺陷,因爲通過 iterator()
方法只能那麼 “隱晦” 地支持一個那樣的對象集合。但是,如果孩子集合比較明顯,Iterable
可以使針對域類型的編程更容易,更直觀。
您是否曾想過以倒序遍歷一個 Collection
?對於這種情況,使用經典的 Java
Collections 算法非常方便。
在上面的 清
單 2
中,Person
的孩子是按照傳入的順序排列的;但是,現在要以相反的順序列出他們。雖然可以編寫另一個
for 循環,按相反順序將每個對象插入到一個新的 ArrayList
中,但是 3、4
次重複這樣做之後,就會覺得很麻煩。
在此情況下,清單 3 中的算法就有了用武之地:
public class ReverseIterator { public static void main(String[] args) { Person ted = new Person("Ted", "Neward", 39, new Person("Michael", "Neward", 16), new Person("Matthew", "Neward", 10)); // Make a copy of the List List<Person> kids = new ArrayList<Person>(ted.getChildren()); // Reverse it Collections.reverse(kids); // Display it System.out.println(kids); } } |
Collections
類有很多這樣的 “算法”,它們被實現爲靜態方法,以 Collections
作爲參數,提供獨立於實現的針對整個集合的行爲。
而且,由於很棒的 API 設計,我們不必完全受限於 Collections
類中提供的算法 —
例如,我喜歡不直接修改(傳入的 Collection 的)內容的方法。所以,可以編寫定製算法是一件很棒的事情,例如清單 4 就是一個這樣的例子:
class MyCollections { public static <T> List<T> reverse(List<T> src) { List<T> results = new ArrayList<T>(src); Collections.reverse(results); return results; } } |
以上定製算法闡釋了關於 Java Collections API 的一個最終觀點:它總是適合加以擴展和修改,以滿足開發人員的特定目的。
例如,假設您需要 Person
類中的孩子總是按年齡排序。雖然可以編寫代碼一遍又一遍地對孩子排序(也許是使用 Collections.sort
方法),但是通過一個
Collection
類來自動排序要好得多。
實際上,您甚至可能不關心是否每次按固定的順序將對象插入到 Collection
中(這正是 List
的基本原理)。您可能只是想讓它們按一定的順序排列。
java.util
中沒有 Collection
類能滿足這些需求,但是編寫一個這樣的類很簡單。只需創建一個接口,用它描述 Collection
應該提供的抽象行爲。對於 SortedCollection
,它的作用完全是行爲方面的。
public interface SortedCollection<E> extends Collection<E> { public Comparator<E> getComparator(); public void setComparator(Comparator<E> comp); } |
編寫這個新接口的實現簡直不值一提:
import java.util.*; public class ArraySortedCollection<E> implements SortedCollection<E>, Iterable<E> { private Comparator<E> comparator; private ArrayList<E> list; public ArraySortedCollection(Comparator<E> c) { this.list = new ArrayList<E>(); this.comparator = c; } public ArraySortedCollection(Collection<? extends E> src, Comparator<E> c) { this.list = new ArrayList<E>(src); this.comparator = c; sortThis(); } public Comparator<E> getComparator() { return comparator; } public void setComparator(Comparator<E> cmp) { comparator = cmp; sortThis(); } public boolean add(E e) { boolean r = list.add(e); sortThis(); return r; } public boolean addAll(Collection<? extends E> ec) { boolean r = list.addAll(ec); sortThis(); return r; } public boolean remove(Object o) { boolean r = list.remove(o); sortThis(); return r; } public boolean removeAll(Collection<?> c) { boolean r = list.removeAll(c); sortThis(); return r; } public boolean retainAll(Collection<?> ec) { boolean r = list.retainAll(ec); sortThis(); return r; } public void clear() { list.clear(); } public boolean contains(Object o) { return list.contains(o); } public boolean containsAll(Collection <?> c) { return list.containsAll(c); } public boolean isEmpty() { return list.isEmpty(); } public Iterator<E> iterator() { return list.iterator(); } public int size() { return list.size(); } public Object[] toArray() { return list.toArray(); } public <T> T[] toArray(T[] a) { return list.toArray(a); } public boolean equals(Object o) { if (o == this) return true; if (o instanceof ArraySortedCollection) { ArraySortedCollection<E> rhs = (ArraySortedCollection<E>)o; return this.list.equals(rhs.list); } return false; } public int hashCode() { return list.hashCode(); } public String toString() { return list.toString(); } private void sortThis() { Collections.sort(list, comparator); } } |
這個實現非常簡陋,編寫時並沒有考慮優化,顯然還需要進行重構。但關鍵是 Java Collections API 從來無意將與集合相關的任何東西定死。它總是需要擴展,同時也鼓勵擴展。
當然,有些擴展比較複雜,例如 java.util.concurrent
中引入的擴展。但是另一些則非常簡單,只需編寫一個定製算法,或者已有 Collection
類的簡單的擴展。
擴展 Java Collections API 看上去很難,但是一旦開始着手,您會發現遠不如想象的那樣難。
和 Java Serialization 一樣,Java Collections API 還有很多角落等待有人去探索 —正因爲如此,我們還不準備結束這個話題。在 5 件事 系列 的下一篇文章中,將可以看到用 Java Collections API 做更多事情的 5 種新的方式。
<!-- CMA ID: 491184 --> <!-- Site ID: 10 --> <!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl -->
描述 | 名字 | 大小 | 下載方法 |
---|---|---|---|
本文樣例代碼 | j-5things2-src.zip | 10KB | HTTP |
學習
- “Introduction
to the Collections Framework
”(MageLang Institute, Sun Developer
Network, 1999):這篇教程是很早以前的,但是很棒,它對併發集合之前的 Java Collections Framework
做了完整的介紹。
- “Java 理論與實踐:
併發集合類
”(Brian Goetz,developerWorks,2003 年 7 月):學習 Doug Lea 的
util.concurrent
包如何爲標準集合類型List
和Map
注入新的活力。 - “使用泛
型和併發改善集合
”(John Zukowski,developerWorks,2008 年 4 月):介紹 Java 6 中 Java
Collections Framework 的變化。
-
Java
Collections Framework
:閱讀 Sun Microsystems 的 Java Collections
Framework 和 API 文檔。
-
developerWorks
Java 技術專區
: 這裏有數百篇關於 Java 編程每個方面的文章。
討論
- 加
入 My developerWorks 社區
。
<!-- MAIN_COLUMN_CONTENT_END --> <!-- INLINE_COMMENTS_START -->