拼吾爱程序人生

首页 » 其他编程 » Java » java api 接口篇(二)下
SystemPoster - 2008-7-6 16:26:00
?编写你自己的 Comparable 类型

  Comparable 接口由一个单一的方法构成:

public interface Comparable {
public int compareTo(Object o);
} 
 
  compareTo 方法将接收对象与特定对象进行比较,并在接收对象小于、等于或大于特定对象时分别返回负整数、空或一个正整数。如果特定对象不能与接收对象相比较,该方法扔出一个ClassCastException. 这是一个表示某人姓名的类(a class representing a person"s name), 它实现了 Comparable:
import java.util.*; 
 
public class Name implements Comparable {
private String firstName, lastName; 
 
public Name(String firstName, String lastName) {
if (firstName==null || lastName==null)
throw new NullPointerException();
this.firstName = firstName;
this.lastName = lastName;
} 
public String firstName() {return firstName;}
public String lastName() {return lastName;} 
 
public boolean equals(Object o) {
if (!(o instanceof Name))
return false;
Name n = (Name)o;
return n.firstName.equals(firstName) &&
n.lastName.equals(lastName);
} 
 
public int hashCode() {
return 31*firstName.hashCode()  lastName.hashCode();
} 
 
public String toString() {return firstName  " "  lastName;} 
 
public int compareTo(Object o) {
Name n = (Name)o;
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp!=0 ? lastCmp :
firstName.compareTo(n.firstName));
}
} 

  为了使这个例子短一些,该类受到了一点限制:它不支持中间名,它要求必须同时具有first name 和 last name, 而这不是在全世界都通用的。尽管如此,这个例子仍有几个重要之处:
Name 对象是不变的( immutable)。作为相等、不变类型的所有其它事情就是如何做的问题,特别是对那些将被用来作为 Sets 中的元素或 Maps 中的键的对象来说,更是如此。如果你对这些 对象集 中的元素或键做了更改,这些 对象集 将中断。  
 
  构造函数可检查它的参数是否为 null。 这可以保证所有的Name 对象都能很好地形成。因而没有其它方法会?  NullPointerException.  
 
  hashCode 方法被重新定义。对重新定义 equals 方法的任意类来说,这是必需的(essential)。 一般约定(general contract)需要 Object.equals. (Equal 对象必须具有相等的哈希代码) 。  
 
  如果特定对象为 null,或一个不适当的类型, equals 方法则返回 false。 在这种情况下, compareTo 方法扔出一个运行时异常。这两个行为都是各自方法的一般约定所必需的。  
 
  toString 方法已被重新定义,从而可以以人们能够读懂的形式打印 Name 。这总是一个好主意,特别是对要被放入对象集 中的对象来说,更有益处。各种 对象集 类型的 toString 方法依赖它们的元素、键和值的 toString 方法。

  由于这一节介绍的是有关元素排序的问题,因而让我们稍微多谈一点 Name 的 compareTo 方法。它实现标准的姓名-排序算法,在该算法中,last name 优先于 first name。这恰恰是你在一个natural ordering(自然排序)中所想要的。 如果自然排序不自然,那才容易引起混乱呢!  
 
  请看 compareTo 是如何被实现的,因为它是相当典型的。首先,你将 Object 参数转换为适当类型; 如果参数类型是不适当的,则会扔出一个适当的异常(ClassCastException);那么你应该比较对象的最重要部分(在此案例中为 last name)。通常,你可以使用该部分的类型的自然排序。 在次案例中,该部分是一个 String, 并且自然的(按词典顺序的)排序正是所要求的。如果比较的结果是空(它表示等同性)之外的其它东西,你就做完了:你可以返回结果。 如果最重要的部分是相等的,你就应该继续比较次重要部分。在此案例中,只有两个部分 (first name and last name)。 如果有更多的部分,你就应该以显而易见的方式继续进行,直到发现两个不相等的部分(否则你就应该比较最不重要的部分),这时,你就可以返回比较结果了。这是 一个建立 Name 对象列表并对它们进行排序的小程序:

import java.util.*; 
 
class NameSort {
public static void main(String args[]) {
Name n[] = {
new Name("John", "Lennon"),
new Name("Karl", "Marx"),
new Name("Groucho", "Marx"),
new Name("Oscar", "Grouch")
};
List l = Arrays.asList(n);
Collections.sort(l);
System.out.println(l);
}
} 
  如果你运行这个程序,以下是它所打印的结果:

[Oscar Grouch, John Lennon, Groucho Marx, Karl Marx]

  对 compareTo 方法的行为有四个限制,我们现在不作一一讨论,因为它们的技术性太强,并且十分枯燥,我们最好将其留在API文本中。但是,所有实现 Comparable 的类都必须接受这些限制的约束,这一点是确实重要的。因此,如果你要编写一个实现Comparable 的类,请读那些有关 Comparable 的文本吧。要试图为违反了那些限制的对象的列表进行排序可能引发不可预知的行为。从技术上讲,这些限制保证了自然排序是实现它的类的对象的部分顺序(partial order)。保证排序被很好地定义是十分必要的。  
 
  比较器(Comparators) 
 
  好,到目前为止,你已经了解了自然排序。那么,如果要对某些对象不按自然顺序进行排序,又会怎么样呢?或者,如果你要为某些不实现 Comparable 的对象进行排序呢?为做这些事情,你需要提供一个Comparator。 Comparator 实际就是一个封装了排序的对象。与 Comparable 接口类似,Comparator 接口由一个的方法构成:

public interface Comparator {
int compare(Object o1, Object o2);
} 
 
  compare 方法比较它的两个参数,当第一个参数小于、等于或大于第二个参数时,分别返回一个负整数、空或正整数。如果其中一个参数具有对 Comparator 不适合的类型,compare 方法则扔出一个 ClassCastException。  
 
  在上一节中的有关 Comparable 的许多内容也适用Comparator。编写一个 compare 方法几乎等同于编写一个compareTo 方法,除前者是把两个参数都当作参数之外。compare 方法必须象Comparable 的 compareTo 方法一样,服从同样的四个"技术限制"。出于同样的原因, Comparator 必须对它所比较的对象诱发一个 partial order(部分顺序)。

  假设你有一个称作 EmployeeRecord 的类:

public class EmployeeRecord implements Comparable {
public Name name();
public int employeeNumber();
public Date hireDate();
...
} 
 
  我们假设 EmployeeRecord 对象的自然排序是对雇员姓名的排序 (就象上一个例子中所定义的)。不幸的是,老板要求我们提出一个按雇员资历排序的列表。这就意味着我们必须做些额外的工作,但是不多。以下是一个将生成所需列表的程序:  
 
import java.util.*; 
 
class EmpSort {
static final Comparator SENIORITY_ORDER = new Comparator() {
public int compare(Object o1, Object o2) {
EmployeeRecord r1 = (EmployeeRecord) o1;
EmployeeRecord r2 = (EmployeeRecord) o2;
return r2.hireDate().compareTo(r1.hireDate());
}
}; 
 
static final Collection employees = ... ; // Employee Database 
 
public static void main(String args[]) {
List emp = new ArrayList(employees);
Collections.sort(emp, SENIORITY_ORDER);
System.out.println(emp);
}
} 
 
  以上程序中的 Comparator 相当简单。它将它的参数转换为EmployeeRecord, 并依赖适用于 hireDate()方法的 Date 的自然排序。请注意:Comparator 将它的第二个参数的雇用-日期传递给第一个参数,而不是按反方向传递。 这是因为,最新雇用的雇员资历最浅:按照雇用-日期排序将使列表成为反向资历-顺序。另一个获得相同结果的方法是:保持参数顺序,但对比较结果求反。  
 
return -r1.hireDate().compareTo(r2.hireDate());  
 
  两种方法同样可取。使用哪一种,全由你自己。  
 
  以上程序中的 Comparator ,在对 List 进行排序时,效果很好。但有一个小的缺陷:它不能被用来对一个排序的 对象集 (如TreeSetM) 进行排序,因为它生成一个严格的部分(strictly partial) 排序。这意味着这个comparator 使不相等的对象相等。特别的,它会使任意两个雇用日期相同的雇员成为相等。当你为一个 List 排序时,这没关系,但当你使用 Comparator 为一个sort排序的对象集 排序时, 这就是致命的了。如果你将多个雇用日期相同的雇员用Comparator插入到一个TreeSet之中,那么只有第一个将被添加到 set,第二个将被作为一个复制元素而忽略。  

  为解决这个问题,你必须做的一切就是修整 Comparator 使之生成一个 total ordering(完整排序)。 换句话说,修整 Comparator 是为了使在使用compare 时被认为相等的唯一元素即是那些在使用equals 时被认为相等的元素。 实现这个目的的途径是做一个两部分(two-part)比较 (就象我们为 Name 做的那样),这里的第一部分是我们真正感兴趣的(此案例中为雇用-日期),而第二部分是可唯一识别的对象属性。在此案例中,雇员号是作为第二部分使用的明显的属性。请看下面的 Comparator :  
 
static final Comparator SENIORITY_ORDER = new Comparator() {
public int compare(Object o1, Object o2) {
EmployeeRecord r1 = (EmployeeRecord) o1;
EmployeeRecord r2 = (EmployeeRecord) o2;
int dateCmp = r2.hireDate().compareTo(r1.hireDate());
if (dateCmp != 0)
return dateCmp;
return (r1.employeeNumber() $#@60; r2.employeeNumber() ? -1 :
(r1.employeeNumber() == r2.employeeNumber() ? 0 : 1));
}
}; 
 
  最后注意一点,你可能被引诱用更简单的程序来替代 Comparator 中最后的 return 语句:  
 
return r1.employeeNumber() - r2.employeeNumber();  
 
  不要这样做,除非你能绝对保证不出现一个负的雇员数!这个技巧不可普遍使用,因为一个带正负号的整数类型,即使再大,也不足以表示两个任意的带正负号的整数的差值。如果 i 是一个大的正整数,j 是一个大的负整数,i-j 将溢出并返回一个负整数。 Comparator 的结果违反了我们一直在讲的四个技术限制之中的一个限制(传递性),并导致可怕而玄妙的故障。 这并不是一个纯技术问题;搞不好,它会伤着你。    
  SortedSet 接口  
 
  SortedSet是一个使其元素维持上升顺序的Set, 它按照元素的自然顺序进行排序,或按照在SortedSet 创建时提供的 Comparator 进行排序(自然顺序和 Comparators 已在前面的有关 Object Ordering 的章节中做过讨论)。除了正常的 Set 操作之外, 该 Set 接口还提供下列操作:

  局域视图: 对 sorted set 执行任意局域操作。


  端点:返回在 sorted set 中的第一个或最后一个元素。
  比较器存取: 返回对 set 进行排序的Comparator 。

  SortedSet 接口如下所示:

public interface SortedSet extends Set {
// Range-view
SortedSet subSet(Object fromElement, Object toElement);
SortedSet headSet(Object toElement);
SortedSet tailSet(Object fromElement); 
 
// Endpoints
Object first();
Object last(); 
 
// Comparator access
Comparator comparator();
} 
 
  Set 操作 
 

  从 Set 继承的 SortedSet 操作在 sorted sets 和正常 sets 上的表现完全相同,只有两个例外:

  由 iterator 操作返回的 Iterator 按顺序遍历 sorted set。

  由 toArray 返回的数组按顺序包括 sorted set 的元素。  
 
  尽管该接口不保证这一点,但 JDK 的 SortedSet 实现的 toString 方法返回一个按顺序包含所有 sorted set 元素的串。  
    
  SortedMap接口  
 

  SortedMap是一个将它的项保持为上升顺序的Map, 它按照键的自然顺序进行排序,或按照在SortedMap 创建时提供的 Comparator 进行排序(自然顺序和 Comparators 已在前面的有关 Object Ordering 的章节中做过讨论)。除了正常的 Map 操作之外, 该 Map 接口还提供下列操作:  
 
  局域视图: 对 sorted Map 执行任意局域操作。

  端点:返回在 sorted Map 中的第一个或最后一个键。

  比较器存取: 返回对 map 进行排序的Comparator 。 public interface SortedMap extends Map { Comparator comparator(); SortedMap subMap(Object fromKey, Object toKey); SortedMap headMap(Object toKey); SortedMap tailMap(Object fromKey); Object first(); Object last(); } 这个接口是SortedSet的 Map等价物  
 
  Map操作

  SortedMap 从 Map 继承的操作在 sorted maps 和正常 maps 上的行为是等同的,只有两个例外:

  由对任意 sorted map 的 对象集视图的 iterator 操作所返回的 Iterator 按顺序遍历对象集。

  由 Collection视图的 toArray 操作所返回的数组按顺序包括键、值或项。

  尽管该接口不保证这一点,但在 JDK 的 SortedMap 实现中的Collection视图的 toString 方法返回一个按顺序包含视图的所有元素的串。  
 
  标准构造函数(Standard Constructors)

  按惯例,所有的 Map 实现都提供一个采用一个 Map 的标准构造函数,SortedMap 实现也不例外。该构造函数创建了一个 SortedMap 对象,它按照它们的键的自然顺序对它的项进行排序。除此之外 ,按惯例,SortedMap 实现还提供另外两个标准构造函数:

  一个构造函数采用一个 Comparator 并返回一个新的(空的)按特定 Comparator 排序的SortedMap。

  另一个构造函数采用一个 SortedMap 并返回一个新的包含与给定的 SortedMap 的映射相同的 SortedMap,
它按照同样的 Comparator进行排序 (或是用元素的自然顺序,如果特定的 SortedMap 也这样做过的话)。

  请注意,决定该构造函数是否比普通 Map 构造函数优先调用的是参数的编译时类型,而不是它的运行时类型! 第一个标准构造函数是用显式 Comparator 创建一个空的 SortedSet 的一般方法。第二个标准构造函数在本质上与标准 Map 构造函数相似:它用同样的排序创建一个 SortedMap 的拷贝,但使用的是一个程序员指定的实现类型。  
 
  与SortedSet的比较

  因为这个接口是 SortedSet 的一个精确的 Map 对等物,所以,在 SortedSet章节 中的所有的惯用程序和代码举例均适用于 SortedMap, 只需一些小的更改。    7 1 2 3 4 5 6 7 8
9

 您可能对 [Java] 的这些文章也感兴趣:

使用Java Swing 创建一个XML编辑器
用 Java 保存位图文件
在JavaScript程序中整合Java函数
Java布局管理器深入讨论
Enterprise bean的开发
推技术聊天室的实现(下)
推技术聊天室的实现(上)
J2EE概述
从商业角度看J2EE与WINDOWS DNA
XML和J2EE的组合技术
简析J2EE应用程序数据库类设计模式
Java中的两个特殊变量this和super
1
查看完整版本: java api 接口篇(二)下
Modify by pin5i DZNT_ExpandPackage 2.1.3295 2007-2009 pin5i.com
 Total Unique Visitors: