|
您覺(jué)得自己懂 Java 編程?事實(shí)上,大多數(shù)程序員對(duì)于 Java 平臺(tái)都是淺嘗輒止,只學(xué)習(xí)了足以完成手頭上任務(wù)的知識(shí)而已。在本 系列 中,Ted Neward 深入挖掘 Java 平臺(tái)的核心功能,揭示一些鮮為人知的事實(shí),幫助您解決最棘手的編程困難。
Collections 非常強(qiáng)大,但是很多變:使用它們要小心,濫用它們會(huì)帶來(lái)風(fēng)險(xiǎn)。 1. List 不同于數(shù)組Java 開(kāi)發(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.javaimport 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.javaimport 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)勢(shì)是不會(huì)在內(nèi)存中保留整個(gè)內(nèi)容,但是有一個(gè)警告就是,它不能 close() 底層文件句柄(每當(dāng)
readLine() 返回 null 時(shí)就關(guān)閉文件句柄,可以修正這一問(wèn)題,但是在
Iterator 沒(méi)有結(jié)束時(shí)不能解決這個(gè)問(wèn)題)。
hashCode()Map 是很好的集合,為我們帶來(lái)了在其他語(yǔ)言(比如 Perl)中經(jīng)??梢?jiàn)的好用的鍵/值對(duì)集合。JDK 以
HashMap 的形式為我們提供了方便的 Map
實(shí)現(xiàn),它在內(nèi)部使用哈希表實(shí)現(xiàn)了對(duì)鍵的對(duì)應(yīng)值的快速查找。但是這里也有一個(gè)小問(wèn)題:支持哈希碼的鍵依賴(lài)于可變字段的內(nèi)容,這樣容易產(chǎn)生 bug,即使最耐心的
Java 開(kāi)發(fā)人員也會(huì)被這些 bug 逼瘋。
假設(shè)清單 3 中的 Person 對(duì)象有一個(gè)常見(jiàn)的 hashCode() (它使用
firstName、lastName 和 age 字段
— 所有字段都不是 final 字段 — 計(jì)算 hashCode()),對(duì)
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.javaimport 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.javaimport 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)); }} |
很顯然,這種方法很糟糕,但是解決方法也很簡(jiǎn)單:永遠(yuǎn)不要將可變對(duì)象類(lèi)型用作 HashMap 中的鍵。
equals() 與
Comparable在瀏覽 Javadoc 時(shí),Java 開(kāi)發(fā)人員常常會(huì)遇到 SortedSet 類(lèi)型(它在 JDK 中唯一的實(shí)現(xiàn)是
TreeSet)。因?yàn)?SortedSet 是 java.util
包中唯一提供某種排序行為的 Collection,所以開(kāi)發(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)確地說(shuō),set 不包含成對(duì)的 e1 和 e2
元素,因此如果 e1.equals(e2),那么最多包含一個(gè) null 元素。
但實(shí)際上似乎并非如此 — 盡管 清單 4 中沒(méi)有相等的
Person 對(duì)象(根據(jù) Person 的 equals()
實(shí)現(xiàn)),但在輸出時(shí)只有三個(gè)對(duì)象出現(xiàn)在 TreeSet 中。
與 set 的有狀態(tài)本質(zhì)相反,TreeSet 要求對(duì)象直接實(shí)現(xiàn) Comparable
或者在構(gòu)造時(shí)傳入 Comparator,它不使用 equals() 比較對(duì)象;它使用
Comparator/Comparable 的 compare 或
compareTo 方法。
因此存儲(chǔ)在 Set 中的對(duì)象有兩種方式確定相等性:大家常用的 equals() 方法和
Comparable/Comparator 方法,采用哪種方法取決于上下文。
更糟的是,簡(jiǎn)單的聲明兩者相等還不夠,因?yàn)橐耘判驗(yàn)槟康牡谋容^不同于以相等性為目的的比較:可以想象一下按姓排序時(shí)兩個(gè)
Person 相等,但是其內(nèi)容卻并不相同。
一定要明白 equals() 和 Comparable.compareTo() 兩者之間的不同
— 實(shí)現(xiàn) Set 時(shí)會(huì)返回 0。甚至在文檔中也要明確兩者的區(qū)別。
Java Collections
庫(kù)中有很多有用之物,如果您能加以利用,它們可以讓您的工作更輕松、更高效。但是發(fā)掘這些有用之物可能有點(diǎn)復(fù)雜,比如只要您不將可變對(duì)象類(lèi)型作為鍵,您就可以用自己的方式使用
HashMap。
至此我們挖掘了 Collections 的一些有用特性,但我們還沒(méi)有挖到金礦:Concurrent Collections,它在 Java 5
中引入。本 系列 的后 5 個(gè)竅門(mén)將關(guān)注
java.util.concurrent。
|
|
來(lái)自: 阿青哥Joe > 《Java生態(tài)系統(tǒng)》