您觉得自己懂 Java 编程?事实上,大多数程序员对于 Java 平台都是浅尝辄止,只学习了足以完成手头上任务的知识而已。在本 系列 中,Ted Neward 深入挖掘 Java 平台的核心功能,揭示一些鲜为人知的事实,帮助您解决最棘手的编程困难。
Collections 非常强大,但是很多变:使用它们要小心,滥用它们会带来风险。 1. List 不同于数组Java 开发人员常常错误地认为 要明白数组与集合的区别需要弄清楚顺序 和位置 的不同。例如, 清单 1. 可变键值
当第三个元素从上面的 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); } } |
此方法的优势是不会在内存中保留整个内容,但是有一个警告就是,它不能 close()
底层文件句柄(每当
readLine()
返回 null 时就关闭文件句柄,可以修正这一问题,但是在
Iterator
没有结束时不能解决这个问题)。
hashCode()
Map
是很好的集合,为我们带来了在其他语言(比如 Perl)中经常可见的好用的键/值对集合。JDK 以
HashMap
的形式为我们提供了方便的 Map
实现,它在内部使用哈希表实现了对键的对应值的快速查找。但是这里也有一个小问题:支持哈希码的键依赖于可变字段的内容,这样容易产生 bug,即使最耐心的
Java 开发人员也会被这些 bug 逼疯。
假设清单 3 中的 Person
对象有一个常见的 hashCode()
(它使用
firstName
、lastName
和 age
字段
— 所有字段都不是 final 字段 — 计算 hashCode()
),对
Map
的 get()
调用会失败并返回 null
:
hashCode()
容易出现
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)); } } |
很显然,这种方法很糟糕,但是解决方法也很简单:永远不要将可变对象类型用作 HashMap
中的键。
equals()
与
Comparable
在浏览 Javadoc 时,Java 开发人员常常会遇到 SortedSet
类型(它在 JDK 中唯一的实现是
TreeSet
)。因为 SortedSet
是 java.util
包中唯一提供某种排序行为的 Collection
,所以开发人员通常直接使用它而不会仔细地研究它。清单 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); } } |
使用上述代码一段时间后,可能会发现这个 Set
的核心特性之一:它不允许重复。该特性在 Set
Javadoc 中进行了介绍。Set
是不包含重复元素的集合。更准确地说,set 不包含成对的 e1 和 e2
元素,因此如果 e1.equals(e2),那么最多包含一个 null 元素。
但实际上似乎并非如此 — 尽管 清单 4 中没有相等的
Person
对象(根据 Person
的 equals()
实现),但在输出时只有三个对象出现在 TreeSet
中。
与 set 的有状态本质相反,TreeSet
要求对象直接实现 Comparable
或者在构造时传入 Comparator
,它不使用 equals()
比较对象;它使用
Comparator/Comparable
的 compare
或
compareTo
方法。
因此存储在 Set
中的对象有两种方式确定相等性:大家常用的 equals()
方法和
Comparable/Comparator
方法,采用哪种方法取决于上下文。
更糟的是,简单的声明两者相等还不够,因为以排序为目的的比较不同于以相等性为目的的比较:可以想象一下按姓排序时两个
Person
相等,但是其内容却并不相同。
一定要明白 equals()
和 Comparable.compareTo()
两者之间的不同
— 实现 Set
时会返回 0。甚至在文档中也要明确两者的区别。
Java Collections
库中有很多有用之物,如果您能加以利用,它们可以让您的工作更轻松、更高效。但是发掘这些有用之物可能有点复杂,比如只要您不将可变对象类型作为键,您就可以用自己的方式使用
HashMap
。
至此我们挖掘了 Collections 的一些有用特性,但我们还没有挖到金矿:Concurrent Collections,它在 Java 5
中引入。本 系列 的后 5 个窍门将关注
java.util.concurrent
。
|
来自: 阿青哥Joe > 《Java生态系统》