您覺得自己懂 Java 編程,?事實(shí)上,,大多數(shù)程序員對于 Java 平臺(tái)都是淺嘗輒止,只學(xué)習(xí)了足以完成手頭上任務(wù)的知識而已,。在本 系列 中,Ted Neward 深入挖掘 Java 平臺(tái)的核心功能,,揭示一些鮮為人知的事實(shí),幫助您解決最棘手的編程困難,。
Collections 非常強(qiáng)大,,但是很多變:使用它們要小心,濫用它們會(huì)帶來風(fēng)險(xiǎn),。 1. List 不同于數(shù)組Java 開發(fā)人員常常錯(cuò)誤地認(rèn)為 要明白數(shù)組與集合的區(qū)別需要弄清楚順序 和位置 的不同,。例如, 清單 1. 可變鍵值
當(dāng)?shù)谌齻€(gè)元素從上面的 2. 令人驚訝的
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | // FileUtils.java import java.io.*; import java.util.*; public class FileUtils { public static Iterable< String > readlines(String filename) throws IOException { final FileReader fr = new FileReader(filename); final BufferedReader br = new BufferedReader(fr); return new Iterable< String >() { public < code >Iterator</ code >< String > iterator() { return new < code >Iterator</ code >< String >() { public boolean hasNext() { return line != null; } public String next() { String retval = line; line = getLine(); return retval; } public void remove() { throw new UnsupportedOperationException(); } String getLine() { String line = null; try { line = br.readLine(); } catch (IOException ioEx) { line = null; } return line; } String line = getLine(); }; } }; } } //DumpApp.java import java.util.*; public class DumpApp { public static void main(String[] args) throws Exception { for (String line : FileUtils.readlines(args[0])) System.out.println(line); } } |
此方法的優(yōu)勢是不會(huì)在內(nèi)存中保留整個(gè)內(nèi)容,,但是有一個(gè)警告就是,它不能 close()
底層文件句柄(每當(dāng)
readLine()
返回 null 時(shí)就關(guān)閉文件句柄,,可以修正這一問題,,但是在
Iterator
沒有結(jié)束時(shí)不能解決這個(gè)問題)。
hashCode()
Map
是很好的集合,,為我們帶來了在其他語言(比如 Perl)中經(jīng)??梢姷暮糜玫逆I/值對集合。JDK 以
HashMap
的形式為我們提供了方便的 Map
實(shí)現(xiàn),,它在內(nèi)部使用哈希表實(shí)現(xiàn)了對鍵的對應(yīng)值的快速查找,。但是這里也有一個(gè)小問題:支持哈希碼的鍵依賴于可變字段的內(nèi)容,,這樣容易產(chǎn)生 bug,即使最耐心的
Java 開發(fā)人員也會(huì)被這些 bug 逼瘋,。
假設(shè)清單 3 中的 Person
對象有一個(gè)常見的 hashCode()
(它使用
firstName
,、lastName
和 age
字段
— 所有字段都不是 final 字段 — 計(jì)算 hashCode()
),對
Map
的 get()
調(diào)用會(huì)失敗并返回 null
:
hashCode()
容易出現(xiàn)
bug1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // 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 kid : kids) children.add(kid); } // ... 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 int hashCode() { return firstName.hashCode() & lastName.hashCode() & age; } // ... private String firstName; private String lastName; private int age; private List< Person > children = new ArrayList< Person >(); } // MissingHash.java import java.util.*; public class MissingHash { public static void main(String[] args) { Person p1 = new Person("Ted", "Neward", 39); Person p2 = new Person("Charlotte", "Neward", 38); System.out.println(p1.hashCode()); Map< Person , Person> map = new HashMap< Person , Person>(); map.put(p1, p2); p1.setLastName("Finkelstein"); System.out.println(p1.hashCode()); System.out.println(map.get(p1)); } } |
很顯然,,這種方法很糟糕,,但是解決方法也很簡單:永遠(yuǎn)不要將可變對象類型用作 HashMap
中的鍵。
equals()
與
Comparable
在瀏覽 Javadoc 時(shí),,Java 開發(fā)人員常常會(huì)遇到 SortedSet
類型(它在 JDK 中唯一的實(shí)現(xiàn)是
TreeSet
),。因?yàn)?SortedSet
是 java.util
包中唯一提供某種排序行為的 Collection
,所以開發(fā)人員通常直接使用它而不會(huì)仔細(xì)地研究它,。清單 4 展示了:
SortedSet
,,我很高興找到了它!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import java.util.*; public class UsingSortedSet { public static void main(String[] args) { List< Person > persons = Arrays.asList( new Person("Ted", "Neward", 39), new Person("Ron", "Reynolds", 39), new Person("Charlotte", "Neward", 38), new Person("Matthew", "McCullough", 18) ); SortedSet ss = new TreeSet(new Comparator< Person >() { public int compare(Person lhs, Person rhs) { return lhs.getLastName().compareTo(rhs.getLastName()); } }); ss.addAll(perons); System.out.println(ss); } } |
使用上述代碼一段時(shí)間后,,可能會(huì)發(fā)現(xiàn)這個(gè) Set
的核心特性之一:它不允許重復(fù),。該特性在 Set
Javadoc 中進(jìn)行了介紹。Set
是不包含重復(fù)元素的集合,。更準(zhǔn)確地說,,set 不包含成對的 e1 和 e2
元素,因此如果 e1.equals(e2),,那么最多包含一個(gè) null 元素,。
但實(shí)際上似乎并非如此 — 盡管 清單 4 中沒有相等的
Person
對象(根據(jù) Person
的 equals()
實(shí)現(xiàn)),但在輸出時(shí)只有三個(gè)對象出現(xiàn)在 TreeSet
中,。
與 set 的有狀態(tài)本質(zhì)相反,,TreeSet
要求對象直接實(shí)現(xiàn) Comparable
或者在構(gòu)造時(shí)傳入 Comparator
,它不使用 equals()
比較對象,;它使用
Comparator/Comparable
的 compare
或
compareTo
方法,。
因此存儲(chǔ)在 Set
中的對象有兩種方式確定相等性:大家常用的 equals()
方法和
Comparable/Comparator
方法,采用哪種方法取決于上下文,。
更糟的是,,簡單的聲明兩者相等還不夠,因?yàn)橐耘判驗(yàn)槟康牡谋容^不同于以相等性為目的的比較:可以想象一下按姓排序時(shí)兩個(gè)
Person
相等,,但是其內(nèi)容卻并不相同,。
一定要明白 equals()
和 Comparable.compareTo()
兩者之間的不同
— 實(shí)現(xiàn) Set
時(shí)會(huì)返回 0。甚至在文檔中也要明確兩者的區(qū)別,。
Java Collections
庫中有很多有用之物,,如果您能加以利用,它們可以讓您的工作更輕松、更高效,。但是發(fā)掘這些有用之物可能有點(diǎn)復(fù)雜,,比如只要您不將可變對象類型作為鍵,您就可以用自己的方式使用
HashMap
,。
至此我們挖掘了 Collections 的一些有用特性,,但我們還沒有挖到金礦:Concurrent Collections,它在 Java 5
中引入,。本 系列 的后 5 個(gè)竅門將關(guān)注
java.util.concurrent
。
|