yxhxj2006

常用链接

统计

最新评论

Java 集合框架

在常见用法中,集合(collection)和数学上直观的集(set)的概念是相同的。 集是一个唯一项组,也就是说组中没有重复项。实际上,“集合框架”包含了一个 Set 接口和许多具体的 Set 类。但正式的集概念却比 Java 技术提前了一个世纪,那时英国数学家 George Boole 按逻辑正式的定义了集的概念。大部分人在小学时通过我们熟悉的维恩图引入的“集的交”和“集的并”学到过一些集的理论。 

集的一些现实的示例如下: 
•大写字母集“A”到“Z” 
•非负整数集{0, 1, 2 ...} 
•保留的 Java 编程语言关键字集 {'import', 'class', 'public', 'protected'...} 
•人集(friends, employees, clients, ...) 
•数据库查询返回记录集 
•Container 的 Component 对象集 
•所有对(pair)集 
•空集{} 

集的基本属性如下: 
•集内只包含每项的一个实例 
•集可以是有限的,也可以是无限的 
•可以定义抽象概念 

集不仅是逻辑学、数学和计算机科学的基础,对于商业和系统的日常应用来说,它也很实用。“连接池”这一概念就是数据库服务器的一个开放连接集。Web 服务器必须管理客户机和连接集。文件描述符提供了操作系统中另一个集的示例。 

映射是一种特别的集。它是一种对(pair)集,每个对表示一个元素到另一元素的单向映射。一些映射示例有: 
•IP 地址到域名(DNS)的映射 
•关键字到数据库记录的映射 
•字典(词到含义的映射) 
•2 进制到 10 进制转换的映射 

就像集一样,映射背后的思想比 Java 编程语言早的多,甚至比计算机科学还早。集和映射是数学领域的重要工具,人们非常了解它们的属性。不仅如此,人们早就认识到用集和映射解决编程问题是有效的。1969 年发明的一种名为 SETL(Set Language)的语言只包含 set 一种基本数据类型(SETL 也包含垃圾收集 ― 这项技术在 20 世纪 90 年代开发了 Java 技术后才被人们普遍接受)。虽然连 C++ 在内的许多语言都包含集和映射,但“集合框架”很可能是设计最完美的集和映射包,并且是为流行的语言而写的。 (C++ 标准模板库(Standard Template Library(STL))和 Smalltalk 的集合层次结构的用户对最后一点可能有争议。) 

此外,因为映射也是集,所以它们可以是有限的,也可以是无限的。 无限映射的一个示例如 2 进制到 10 进制的转换。不幸的是,“集合框架”不支持无限映射 ― 有时用数学函数、公式或算法更好。但在有限映射能解决问题时,“集合框架”会给 Java 程序员提供一个有用的 API。 

因为“集合框架”有类 Set、Collection 和 Map 的正规定义,您会注意到小写的词 set、collection 和 map 把实现和概念区分开来。 

集合接口和类 

既然您已经具备了一些集的理论,您应该能够更轻松的理解“集合框架”。 “集合框架”由一组用来操作对象的接口组成。不同接口描述不同类型的组。 在很大程度上,一旦您理解了接口,您就理解了框架。 虽然您总要创建接口特定的实现,但访问实际集合的方法应该限制在接口方法的使用上;因此,允许您更改基本的数据结构而不必改变其它代码。框架接口层次结构如下图所示。 

有的人可能会认为 Map 会继承 Collection。 在数学中,映射只是对(pair)的集合。但是,在“集合框架”中,接口 Map 和 Collection 在层次结构没有任何亲缘关系,它们是截然不同的。这种差别的原因与 Set 和 Map 在 Java 库中使用的方法有关。Map 的典型应用是访问按关键字存储的值。它支持一系列集合操作的全部,但操作的是键-值对,而不是单个独立的元素。因此 Map 需要支持 get() 和 put() 的基本操作,而 Set 不需要。此外,还有返回 Map 对象的 Set 视图的方法: 
Set set = aMap.keySet(); 
用“集合框架”设计软件时,记住该框架四个基本接口的下列层次结构关系会有用处: 
•Collection 接口是一组允许重复的对象。 
•Set 接口继承 Collection,但不允许重复。 
•List 接口继承 Collection,允许重复,并引入位置下标。 
•Map 接口既不继承 Set 也不继承 Collection。 

让我们转到对框架实现的研究,具体的集合类遵循命名约定,并将基本数据结构和框架接口相结合。除了四个历史集合类外,Java 2 框架还引入了六个集合实现,如下表所示。关于历史集合类如何转换、比如说,如何修改 Hashtable并结合到框架中,请参阅历史集合类 。 

接口 实现 历史集合类 
Set HashSet 
TreeSet 
List ArrayList Vector 
LinkedList Stack 
Map HashMap Hashtable 
TreeMap Properties 

这里没有 Collection 接口的实现。历史集合类,之所以这样命名是因为从 Java 类库 1.0 发行版就开始沿用至今了。 

如果从历史集合类转换到新的框架类,主要差异之一在于所有的操作都和新类不同步。您可以往新类中添加同步的实现,但您不能把它从旧的类中除去。 


-------------------------------------------------------------------------------- 


集合接口 

Collection 接口用于表示任何对象或元素组。想要尽可能以常规方式处理一组元素时,就使用这一接口。这里是以统一建模语言(Unified Modeling Language(UML))表示法表示的 Collection 公有方法清单。 

该接口支持如添加和除去等基本操作。设法除去一个元素时,如果这个元素存在,除去的仅仅是集合中此元素的一个实例。 
•boolean add(Object element) 
•boolean remove(Object element) 

Collection 接口还支持查询操作: 
•int size() 
•boolean isEmpty() 
•boolean contains(Object element) 
•Iterator iterator() 

Iterator 接口 

Collection 接口的 iterator() 方法返回一个 Iterator。Iterator 和您可能已经熟悉的 Enumeration接口类似,我们将在Enumeration 接口中对 Enumeration 进行讨论。使用 Iterator 接口方法,您可以从头至尾遍历集合,并安全的从底层 Collection 中除去元素: 

remove() 方法可由底层集合有选择的支持。当底层集合调用并支持该方法时,最近一次 next() 调用返回的元素就被除去。为演示这一点,用于常规 Collection 的 Iterator 接口代码如下: 
Collection collection = ...; 
Iterator iterator = collection.iterator(); 
while (iterator.hasNext()) { 
Object element = iterator.next(); 
if (removalCheck(element)) { 
iterator.remove(); 



组操作 

Collection 接口支持的其它操作,要么是作用于元素组的任务,要么是同时作用于整个集合的任务。 
•boolean containsAll(Collection collection) 
•boolean addAll(Collection collection) 
•void clear() 
•void removeAll(Collection collection) 
•void retainAll(Collection collection) 

containsAll() 方法允许您查找当前集合是否包含了另一个集合的所有元素,即另一个集合是否是当前集合的子集。其余方法是可选的,因为特定的集合可能不支持集合更改。addAll() 方法确保另一个集合中的所有元素都被添加到当前的集合中,通常称为并。clear() 方法从当前集合中除去所有元素。removeAll() 方法类似于 clear(),但只除去了元素的一个子集。retainAll() 方法类似于 removeAll() 方法,不过可能感到它所做的与前面正好相反:它从当前集合中除去不属于另一个集合的元素,即交。 

剩下的两种将 Collection转换成数组的接口方法,将在新集合到历史集合的转换中讨论。 

AbstractCollection 类 

AbstractCollection 类提供具体“集合框架”类的基本功能。虽然您可以自行实现 Collection 接口的所有方法,但是,除了iterator() 和 size() 方法在恰当的子类中实现以外,其它所有方法都由 AbstractCollection 类来提供实现。如果子类不覆盖某些方法,可选的如 add() 之类的方法将抛出异常。 

“集合框架”设计的相关事项 

在创建“集合框架”的过程中,我们需要 Sun 开发小组提供用于操作元素组的灵活接口。 为了使设计简单,我们不为每个可选的功能分别提供独立的接口,而是提供了一个定义所有方法的接口。而这些方法是一个实现类才有可能提供的。然而,有些接口方法是可选的。因为一个接口实现必须实现所有接口方法,调用程序就需要一种途径来知道一个可选的方法是不是不受支持。调用一种可选方法时,框架开发小组所选择去通知调用程序的方式是抛出一个 UnsupportedOperationException。 如果在使用集合的过程中,一个 UnsupportedOperationException 被抛出,则操作失败,因为它不受支持。UnsupportedOperationException 类继承 RuntimeException 类避免了不得不把所有集合操作放入 try-catch 块。 

除了借助运行时异常处理可选操作外,具体集合实现的迭代器还是 故障快速(fail-fast)的。这意味着,当另一个线程修改底层集合的时候,如果您正在用 Iterator 遍历集合,那么,Iterator就会抛出 ConcurrentModificationException (另一种 RuntimeException)异常立刻失败。就是说,下次调用 Iterator 方法时底层集合已修改过了,ConcurrentModificationException 异常就抛出了。 

-------------------------------------------------------------------------------- 

Set 接口 

按照定义,Set 接口继承 Collection 接口,而且它不允许集合中存在重复项。所有原始方法都是现成的,没有引入新方法。具体的 Set 实现类依赖添加的对象的 equals() 方法来检查等同性。 

HashSet 类和 TreeSet 类 

“集合框架”支持 Set 接口两种普通的实现:HashSet 和 TreeSet。在更多情况下,您会使用 HashSet 存储重复自由的集合。考虑到效率,添加到 HashSet 的对象需要采用恰当分配散列码的方式来实现 hashCode() 方法。虽然大多数系统类覆盖了 Object 中缺省的 hashCode() 实现,但创建您自己的要添加到 HashSet 的类时,别忘了覆盖 hashCode()。当您要从集合中以有序的方式抽取元素时,TreeSet 实现会有用处。为了能顺利进行,添加到 TreeSet 的元素必须是可排序的。 “集合框架”添加对 Comparable元素的支持,在排序的“可比较的接口”部分中会详细介绍。我们暂且假定一棵树知道如何保持 java.lang 包装程序器类元素的有序状态。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。 

为优化 HashSet 空间的使用,您可以调优初始容量和负载因子。TreeSet 不包含调优选项,因为树总是平衡的,保证了插入、删除、查询的性能为 log(n)。 

HashSet 和 TreeSet 都实现 Cloneable 接口。 

集的使用示例 

为演示具体 Set 类的使用,下面的程序创建了一个 HashSet,并往里添加了一组名字,其中有个名字添加了两次。接着,程序把集中名字的列表打印出来,演示了重复的名字没有出现。 接着,程序把集作为 TreeSet 来处理,并显示有序的列表。 
import java.util.*; 

public class SetExample { 
public static void main(String args[]) { 
Set set = new HashSet(); 
set.add("Bernadine"); 
set.add("Elizabeth"); 
set.add("Gene"); 
set.add("Elizabeth"); 
set.add("Clara"); 
System.out.println(set); 
Set sortedSet = new TreeSet(set); 
System.out.println(sortedSet); 


运行程序产生了以下输出。请注意重复的条目只出现了一次,列表的第二次输出已按字母顺序排序。 
[Gene, Clara, Bernadine, Elizabeth] 
[Bernadine, Clara, Elizabeth, Gene] 

AbstractSet 类 

AbstractSet 类覆盖了 equals() 和 hashCode() 方法,以确保两个相等的集返回相同的散列码。若两个集大小相等且包含相同元素,则这两个集相等。 按定义,集散列码是集中元素散列码的总和。 因此,不论集的内部顺序如何,两个相等的集会报告相同的散列码。 


-------------------------------------------------------------------------------- 


练系 
•练习 1. 如何将 HashSet 用于一个稀疏的位集 
•练习 2. 如何使用 TreeSet 提供有序的 JList 


-------------------------------------------------------------------------------- 


List 接口 

List 接口继承了 Collection 接口以定义一个允许重复项的有序集合。 该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。 

面向位置的操作包括插入某个元素或 Collection 的功能,还包括获取、除去或更改元素的功能。 在 List 中搜索元素可以从列表的头部或尾部开始,如果找到元素,还将报告元素所在的位置。 
•void add(int index, Object element) 
•boolean addAll(int index, Collection collection) 
•Object get(int index) 
•int indexOf(Object element) 
•int lastIndexOf(Object element) 
•Object remove(int index) 
•Object set(int index, Object element) 

List 接口不但以位置友好的方式遍历整个列表,还能处理集合的子集: 
•ListIterator listIterator() 
•ListIterator listIterator(int startIndex) 
•List subList(int fromIndex, int toIndex) 

处理 subList() 时,位于 fromIndex 的元素在子列表中,而位于 toIndex 的元素则不是,提醒这一点很重要。以下 for-loop 测试案例大致反映了这一点: 
for (int i=fromIndex; i<toIndex; i++) { >
// process element at position i 

此外,我们还应该提醒的是 ― 对子列表的更改(如 add()、remove() 和 set() 调用)对底层 List 也有影响。 

ListIterator 接口 

ListIterator 接口继承 Iterator 接口以支持添加或更改底层集合中的元素,还支持双向访问。 

以下源代码演示了列表中的反向循环。请注意 ListIterator 最初位于列表尾之后(list.size()),因为第一个元素的下标是 0。 
List list = ...; 
ListIterator iterator = list.listIterator(list.size()); 
while (iterator.hasPrevious()) { 
Object element = iterator.previous(); 
// Process element 

正常情况下,不用 ListIterator 改变某次遍历集合元素的方向 ― 向前或者向后。 虽然在技术上可能实现时,但在 previous() 后立刻调用 next(),返回的是同一个元素。把调用 next() 和 previous() 的顺序颠倒一下,结果相同。 

我们还需要稍微再解释一下 add() 操作。添加一个元素会导致新元素立刻被添加到隐式光标的前面。因此,添加元素后调用 previous() 会返回新元素,而调用 next() 则不起作用,返回添加操作之前的下一个元素。 

ArrayList 类和 LinkedList 类 

在“集合框架”中有两种常规的 List 实现:ArrayList 和 LinkedList。 使用两种 List 实现的哪一种取决于您特定的需要。如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素, 那么,ArrayList 提供了可选的集合。 但如果,您要频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么,LinkedList 实现更好。 

ArrayList 和 LinkedList 都实现 Cloneable 接口。此外,LinkedList 添加了一些处理列表两端元素的方法(下图只显示了新方法): 

使用这些新方法,您就可以轻松的把 LinkedList 当作一个堆栈、队列或其它面向端点的数据结构。 
LinkedList queue = ...; 
queue.addFirst(element); 
Object object = queue.removeLast();LinkedList stack = ...; 
stack.addFirst(element); 
Object object = stack.removeFirst(); 
Vector 类和 Stack 类是 List接口的历史实现。我们将在Vector 类和 Stack 类中讨论它们。 

List 的使用示例 

下面的程序演示了具体 List 类的使用。第一部分,创建一个由 ArrayList 支持的 List。填充完列表以后,特定条目就得到了。示例的 LinkedList 部分把 LinkedList 当作一个队列,从队列头部添加东西,从尾部除去。 
import java.util.*; 

public class ListExample { 
public static void main(String args[]) { 
List list = new ArrayList(); 
list.add("Bernadine"); 
list.add("Elizabeth"); 
list.add("Gene"); 
list.add("Elizabeth"); 
list.add("Clara"); 
System.out.println(list); 
System.out.println("2: " + list.get(2)); 
System.out.println("0: " + list.get(0)); 
LinkedList queue = new LinkedList(); 
queue.addFirst("Bernadine"); 
queue.addFirst("Elizabeth"); 
queue.addFirst("Gene"); 
queue.addFirst("Elizabeth"); 
queue.addFirst("Clara"); 
System.out.println(queue); 
queue.removeLast(); 
queue.removeLast(); 
System.out.println(queue); 


运行程序产生了以下输出。请注意,与 Set 不同的是 List 允许重复。 
[Bernadine, Elizabeth, Gene, Elizabeth, Clara] 
2: Gene 
0: Bernadine 
[Clara, Elizabeth, Gene, Elizabeth, Bernadine] 
[Clara, Elizabeth, Gene] 
AbstractList 类和 AbstractSequentialList 类 

有两个抽象的 List 实现类:AbstractList 和 AbstractSequentialList。像 AbstractSet 类一样,它们覆盖了 equals() 和 hashCode() 方法以确保两个相等的集合返回相同的散列码。若两个集大小相等且包含顺序相同的相同元素,则这两个集相等。 这里的 hashCode() 实现在 List 接口定义中指定,而在这里实现。 

除了 equals() 和 hashCode() 实现,AbstractList 和 AbstractSequentialList 实现了其余 List 方法的一部分。因为数据源随机访问和顺序访问是分别实现的,使得具体列表实现的创建更为容易。需要定义的一套方法取决于您希望支持的行为。下表应该能够帮您记住需要实现哪些方法。 您永远不必亲自提供的是 Iterator iterator() 方法的实现。 

AbstractList AbstractSequentialList 


不可修改 Object get(int index) ListIterator listIterator(int index) 
int size() - boolean hasNext() 
- Object next() 
- int nextIndex() 
- boolean hasPrevious() 
- Object previous() 
- int previousIndex() 
int size() 


可修改 Object get(int index) ListIterator listIterator(int index) 
int size() - boolean hasNext() 
Object set(int index, Object element) - Object next() 
- int nextIndex() 
- boolean hasPrevious() 
- Object previous() 
- int previousIndex() 
int size() 
ListIterator 
- set(Object element) 

变量大小和可修改性 Object get(int index) ListIterator listIterator(int index) 
int size() - boolean hasNext() 
Object set(int index, Object element) - Object next() 
add(int index, Object element) - int nextIndex() 
Object remove(int index) - boolean hasPrevious() 
- Object previous() 
- int previousIndex() 
int size() 
ListIterator 
- set(Object element) 
ListIterator 
- add(Object element) 
- remove() 

正如 Collection 接口文档所述,您还应该提供两个构造函数,一个无参数,一个接受另一个 Collection。 

-------------------------------------------------------------------------------- 

练习 
•练习 3. 如何用 JComboBox 实现 ArrayList 

-------------------------------------------------------------------------------- 

Map 接口 

Map 接口不是 Collection 接口的继承。而是从自己的用于维护键-值关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。 

我们可以把这个接口方法分成三组操作:改变、查询和提供可选视图。 

改变操作允许您从映射中添加和除去键-值对。键和值都可以为 null。但是,您不能把 Map 作为一个键或值添加给自身。 
•Object put(Object key, Object value) 
•Object remove(Object key) 
•void putAll(Map mapping) 
•void clear() 

查询操作允许您检查映射内容: 
•Object get(Object key) 
•boolean containsKey(Object key) 
•boolean containsValue(Object value) 
•int size() 
•boolean isEmpty() 

最后一组方法允许您把键或值的组作为集合来处理。 
•public Set keySet() 
•public Collection values() 
•public Set entrySet() 

因为映射中键的集合必须是唯一的,您用 Set 支持。因为映射中值的集合可能不唯一,您用 Collection 支持。最后一个方法返回一个实现 Map.Entry 接口的元素 Set。 

Map.Entry 接口 

Map 的 entrySet() 方法返回一个实现 Map.Entry 接口的对象集合。集合中每个对象都是底层 Map 中一个特定的键-值对。 

通过这个集合迭代,您可以获得每一条目的键或值并对值进行更改。但是,如果底层 Map 在 Map.Entry 接口的 setValue() 方法外部被修改,此条目集就会变得无效,并导致迭代器行为未定义。 

HashMap 类和 TreeMap 类 

“集合框架”提供两种常规的 Map 实现:HashMap 和 TreeMap。和所有的具体实现一样,使用哪种实现取决于您的特定需要。 在 Map 中插入、删除和定位元素,HashMap 是最好的选择。 但如果您要按顺序遍历键,那么 TreeMap 会更好。根据集合大小,先把元素添加到 HashMap,再把这种映射转换成一个用于有序键遍历的 TreeMap 可能更快。使用 HashMap 要求添加的键类明确定义了 hashCode() 实现。有了 TreeMap实现,添加到映射的元素一定是可排序的。我们将在排序中详细介绍。 

为了优化 HashMap 空间的使用,您可以调优初始容量和负载因子。这个 TreeMap 没有调优选项,因为该树总处于平衡状态。 

HashMap 和 TreeMap 都实现 Cloneable 接口。 

Hashtable 类和 Properties 类是 Map接口的历史实现。我们将在Dictionary 类、Hashtable 类和 Properties 类中讨论。 

映射的使用示例 

以下程序演示了具体 Map 类的使用。该程序对自命令行传递的词进行频率计数。HashMap 起初用于数据存储。后来,映射被转换为 TreeMap 以显示有序的键列列表。 
import java.util.*; 

public class MapExample { 
public static void main(String args[]) { 
Map map = new HashMap(); 
Integer ONE = new Integer(1); 
for (int i=0, n=args.length; i<n; i++) { >
String key = args[i]; 
Integer frequency = (Integer)map.get(key); 
if (frequency == null) { 
frequency = ONE; 
} else { 
int value = frequency.intValue(); 
frequency = new Integer(value + 1); 

map.put(key, frequency); 

System.out.println(map); 
Map sortedMap = new TreeMap(map); 
System.out.println(sortedMap); 


用 Bill of Rights 的第三篇文章的文本运行程序产生下列输出,请注意有序输出看起来多么有用! 

无序输出: 

{prescribed=1, a=1, time=2, any=1, no=1, shall=1, nor=1, peace=1, owner=1, soldier=1, to=1, the=2, law=1, but=1, manner=1, without=1, house=1, in=4, by=1, consent=1, war=1, quartered=1, be=2, of=3} 

有序输出: 

{a=1, any=1, be=2, but=1, by=1, consent=1, house=1, in=4, law=1, manner=1, no=1, nor=1, of=3, owner=1, peace=1, prescribed=1, quartered=1, shall=1, soldier=1, the=2, time=2, to=1, war=1, without=1} 

AbstractMap 类 

和其它抽象集合实现相似,AbstractMap 类覆盖了 equals() 和 hashCode() 方法以确保两个相等映射返回相同的散列码。如果两个映射大小相等、包含同样的键且每个键在这两个映射中对应的值都相同,则这两个映射相等。按定义,映射的散列码是映射元素散列码的总和, 其中每个元素是 Map.Entry 接口的一个实现。因此,不论映射内部顺序如何,两个相等映射会报告相同的散列码。 

WeakHashMap 类 

WeakHashMap 是 Map 的一个特殊实现,它只用于存储对键的弱引用。 当映射的某个键在 WeakHashMap 的外部不再被引用时,就允许垃圾收集器收集映射中相应的键值对。 使用 WeakHashMap 有益于保持类似注册表的数据结构,其中条目的键不再能被任何线程访问时,此条目就没用了。 

Java 2 SDK,标准版,版本 1.3 添加了一个构造函数到 WeakHashMap,它接受 Map。在 Java 2 平台版本 1.2 中,该构造函数只允许覆盖缺省负载因子和初始容量设置,不允许在另一个映射的基础上初始化映射(如 Map 接口文档中所介绍)。 


-------------------------------------------------------------------------------- 

排序 

为了用“集合框架”的额外部分把排序支持添加到 Java 2 SDK,版本 1.2,核心 Java 库作了许多更改。 像 String 和 Integer 类如今实现 Comparable 接口以提供自然排序顺序。 对于那些没有自然顺序的类、或者当您想要一个不同于自然顺序的顺序时,您可以实现 Comparator 接口来定义您自己的。 

为了利用排序功能,“集合框架”提供了两种使用该功能的接口:SortedSet 和 SortedMap。 

Comparable 接口 

在 java.lang 包中,Comparable 接口适用于一个类有自然顺序的时候。假定对象集合是同一类型,该接口允许您把集合排序成自然顺序。 

compareTo() 方法比较当前实例和作为参数传入的元素。如果排序过程中当前实例出现在参数前,就返回某个负值。如果当前实例出现在参数后,则返回正值。否则,返回零。这里不要求零返回值表示元素相等。零返回值只是表示两个对象排在同一个位置。 

在 Java 2 SDK,版本 1.2 中有十四个类实现 Comparable 接口。下表展示了它们的自然排序。 虽然一些类共享同一种自然排序,但只有相互可比的类才能排序。 

类 排序 

BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short 按数字大小排序 

Character 按 Unicode 值的数字大小排序 

CollationKey 按语言环境敏感的字符串排序 

Date 按年代排序 

File 按系统特定的路径名的全限定字符的 Unicode 值排序 

ObjectStreamField 按名字中字符的 Unicode 值排序 

String 按字符串中字符 Unicode 值排序 


String 的 compareTo() 方法的文档按词典的形式定义了排序。这意味着比较只是在文本中被数字化了的字符串值之间,其中文本没有必要按所有语言的字母顺序排序。 对于语言环境特定的排序,通过 CollationKey 使用 Collator。 

下面演示了通过 CollationKey 使用 Collator 进行语言环境特定的排序: 
import java.text.*; 
import java.util.*; 

public class CollatorTest { 
public static void main(String args[]) { 
Collator collator = Collator.getInstance(); 
CollationKey key1 = collator.getCollationKey("Tom"); 
CollationKey key2 = collator.getCollationKey("tom"); 
CollationKey key3 = collator.getCollationKey("thom"); 
CollationKey key4 = collator.getCollationKey("Thom"); 
CollationKey key5 = collator.getCollationKey("Thomas"); 

Set set = new TreeSet(); 
set.add(key1); 
set.add(key2); 
set.add(key3); 
set.add(key4); 
set.add(key5); 
printCollection(set); 

static private void printCollection( 
Collection collection) { 
boolean first = true; 
Iterator iterator = collection.iterator(); 
System.out.print("["); 
while (iterator.hasNext()) { 
if (first) { 
first = false; 
} else { 
System.out.print(", "); 

CollationKey key = (CollationKey)iterator.next(); 
System.out.print(key.getSourceString()); 

System.out.println("]"); 


运行程序产生了以下输出。 
[thom, Thom, Thomas, tom, Tom] 
如果没有用 Collator,而是直接的存储名字,那么小写的名字会和大写的名字分开显示: 
[Thom, Thomas, Tom, thom, tom] 
创建您自己的类 Comparable 只是个实现 compareTo() 方法的问题。通常就是依赖几个数据成员的自然排序。您自己的类也应该覆盖 equals() 和 hashCode() 以确保两个相等的对象返回同一个散列码。 

Comparator 接口 

若一个类不能用于实现 java.lang.Comparable,您可以提供自己的 java.util.Comparator 行为。如果您不喜欢缺省的 Comparable 行为,您照样可以提供自己的 Comparator。 

Comparator 的 compare() 方法的返回值和 Comparable 的 compareTo() 方法的返回值相似。在此情况下,如果排序时第一个元素出现在第二个元素之前,则返回一个负值。如果第一个元素出现在后,那么返回一个正值。 否则,返回零。与 Comparable 相似,零返回值不表示元素相等。一个零返回值只是表示两个对象排在同一位置。由 Comparator 用户决定如何处理。 如果两个不相等的元素比较的结果为零,您首先应该确信那就是您要的结果,然后记录行为。 

为了演示,您会发现编写一个新的忽略大小写的 Comparator,代替使用 Collator 进行语言环境特定、忽略大小写的比较会更容易。这样的一种实现如下所示: 
class CaseInsensitiveComparator implements Comparator { 
public int compare(Object element1, Object element2) { 
String lowerE1 = ((String)element1).toLowerCase(); 
String lowerE2 = ((String)element2).toLowerCase(); 
return lowerE1.compareTo(lowerE2); 


因为每个类在某些地方都建立了 Object 子类,所以这不是您实现 equals() 方法的必要条件。实际上大多数情况下您不会去这样做。切记该 equals() 方法检查的是 Comparator 实现的等同性,不是处于比较状态下的对象。 

Collections 类有个预定义的 Comparator 用于重用。 调用 Collections.reverseOrder() 返回一个 Comparator,它对逆序实现 Comparable 接口的对象进行排序。 

练习 
•练习 4. 如何使用映射对词计数 

SortedSet 接口 

“集合框架”提供了个特殊的 Set 接口:SortedSet,它保持元素的有序顺序。 

该接口为集的子集和它的两端(即头和尾)提供了访问方法。当您处理列表的子集时,更改子集会反映到源集。 此外,更改源集也会反映在子集上。发生这种情况的原因在于子集由两端的元素而不是下标元素指定。 此外,如果 fromElement 是源集的一部分,它就是子集的一部分。但如果 toElement 是源集的一部分,它却不是子集的一部分。如果您想要一个特殊的高端元素(to-element)在子集中,您必须找到下一个元素。对于一个 String 来说,下一个元素是个附带空字符的同一个字符串(string+"\0")。; 

添加到 SortedSet 的元素必须实现 Comparable,否则您必须给它的实现类的构造函数提供一个 Comparator:TreeSet(您可以自己实现接口。但是“集合框架”只提供这样一个具体的实现类。) 

为了演示,以下示例使用 Collections 类中逆序的 Comparator。 
Comparator comparator = Collections.reverseOrder(); 
Set reverseSet = new TreeSet(comparator); 
reverseSet.add("Bernadine"); 
reverseSet.add("Elizabeth"); 
reverseSet.add("Gene"); 
reverseSet.add("Elizabeth"); 
reverseSet.add("Clara"); 
System.out.println(reverseSet); 
运行程序产生了以下输出。 
[Gene, Elizabeth, Clara, Bernadine] 
因为集必须包含唯一的项,如果添加元素时比较两个元素导致了零返回值(通过 Comparable 的 compareTo() 方法或 Comparator 的 compare() 方法)那么新元素就没有添加进去。如果两个元素相等,那还好。但如果它们不相等的话,您接下来就应该修改比较方法,让比较方法和 equals() 方法一致。 

使用先前的 CaseInsensitiveComparator 演示这一问题,产生了一个三元素集:thom、Thomas 和 Tom,而不是可能预期的五个元素。 
Comparator comparator = new CaseInsensitiveComparator(); 
Set set = new TreeSet(comparator); 
set.add("Tom"); 
set.add("tom"); 
set.add("thom"); 
set.add("Thom"); 
set.add("Thomas"); 

SortedMap 接口 

“集合框架”提供了个特殊的Map 接口:SortedMap,它用来保持键的有序顺序。 

此接口为映射的子集包括两个端点提供了访问方法。除了排序是作用于映射的键以外,处理 SortedMap 和处理 SortedSet 一样。“集合框架”提供的实现类是 TreeMap。 

因为对于映射来说,每个键只能对应一个值,如果在添加一个键-值对时比较两个键产生了零返回值(通过 Comparable 的 compareTo() 方法或通过 Comparator 的 compare() 方法),那么,原始键对应值被新的值替代。如果两个元素相等,那还好。 但如果不相等,那么您就应该修改比较方法,让比较方法和 equals() 的效果一致。 

posted on 2012-09-20 00:42 奋斗成就男人 阅读(398) 评论(0)  编辑  收藏 所属分类: java


只有注册用户登录后才能发表评论。


网站导航: