Java 比较器

摘要:Java中Arrays以及支持可排序功能的集合类等都提供了对象比较功能,那么它是如何实现的呢,能直接使用Arrays来比较两个自定义的对象数组吗,答案是否定的。下面本文从Arrays类出发,介绍对象数组的比较功能,以及两种比较器的实现方式。

Arrays类提供的比较功能

java.util包中Arrays类作为一个工具类,提供了大量用于数组操作的静态方法,其中包括数组二分查找,对象数组的比较以及对象数组的排序等功能,供用户方便使用。

使用下面的例子对两个int数组进行比较如下:

public class TestCompare{
	public static void main(String[] args) {
		int a[] = {1,2,3,4,5,6};
		int b[] = {5,2,4,1,6,3};

		Arrays.sort(b);
		System.out.println(Arrays.equals(a,b));
	}
}

答案返回true,如果将int更改其包装类Integer,再试一下结果还是一样的,可以排序和比较成功。如果换成我们自己定义的类呢。下面是自定义的Student类,包含名字,年龄,成绩属性,现在希望使用Arrays对Student对象数组进行排序,又会怎样呢?

class Student{
 	private String name = null;
 	private int age = 0;
 	private double score = 0.0;

 	public Student(){

 	}

 	public Student(String name,int age,double score){
  		this.name = name;
  		this.age = age;
  		this.score = score;
 	}

	public String toString(){
  		return "name=" + this.name + " age=" + this.age + " score=" + this.score;
 	}
}

然后在主函数中定义一个Student对象数组,并对其进行比较,输出。

Student stu[] = {new Student("zhangsan",10,88.5),new Student("lisi",12,92.3),
new Student("wangwu",11,85.6),new Student("zhaoliu",12,94.5)};

System.out.println("before sort:");
for(int i=0;i<stu.length;i++){
	System.out.println(stu[i]);
}
Arrays.sort(stu);
System.out.println("after sort:");
for(int i=0;i<stu.length;i++){
	System.out.println(stu[i]);
}

比较之前,可以正常输出对象数组信息,比较没有成功返回结果,而是打印出如下信息:

before sort: name=zhangsan age=10 score=88.5 name=lisi age=12 score=92.3 name=wangwu age=11 score=85.6 name=zhaoliu age=12 score=94.5 Exception in thread “main” java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable at java.util.ComparableTimSort.countRunAndMakeAscending(Unknown Source) at java.util.ComparableTimSort.sort(Unknown Source) at java.util.ComparableTimSort.sort(Unknown Source) at java.util.Arrays.sort(Unknown Source) at TestCompare.main(TestCompare.java:18)

为什么会出现以上类型转换异常呢,再回过头来看看Arrays的sort方法是如何支持对象数组的比较的。

public static void sort([] a)

Sorts the specified array of objects into ascending order, according to the natural ordering of its elements. All elements in the array must implement the Comparable interface. Furthermore, all elements in the array must be mutually comparable (that is, e1.compareTo(e2) must not throw a ClassCastException for any elements e1 and e2 in the array).

类本身实现Comparable接口实现比较

从以上描述可以看出,Arrays的sort方法默认对数组中对象以升序排序,以一个自然顺序,那么这个自然顺序的定义必须使用Comparable接口来完成,即如果要实现对象数组的排序,则此类必须实现Comparable接口,并覆写compareTo方法。下面通过改造Student类,使其实现Comparable接口,并覆写compareTo方法如下:

class Student implements Comparable<Student> {
	private String name = null;
	private int age = 0;
	private double score = 0.0;

	public Student(){

	}

	public Student(String name,int age,double score){
		this.name = name;
		this.age = age;
		this.score = score;
	}

	public int compareTo(Student stu){
		if(this.score > stu.score){
			return 1;
		}else if(this.score < stu.score){
			return -1;
		}else{
			return 0;
		}
	}

	public String toString(){
		return "name=" + this.name + " age=" + this.age + " score=" + this.score;
	}
}

再次在主函数中使用Arrays的sort对Student数组进行排序,看有什么改变,输出如下:

before sort: name=zhangsan age=10 score=88.5 name=lisi age=12 score=92.3 name=wangwu age=11 score=85.6 name=zhaoliu age=12 score=94.5 after sort: name=wangwu age=11 score=85.6 name=zhangsan age=10 score=88.5 name=lisi age=12 score=92.3 name=zhaoliu age=12 score=94.5

程序成功实现了成绩的升序排序,要实现降序,可以在compareTo方法中对调1和-1即可,可以发现Comparable接口支持泛型,可以帮助我们进行类型检查。实际上这种Comparable接口的 compareTo方法有点类似于二叉排序树,下面就Comparable接口定义一个二叉排序树,如下:(实际上这部分内容属于数据结构,但是从此例子可以清晰发现Comparable与二叉排序树的一致性)

class BinarrySortTree{

	private class Node{
		private Comparable data;
		private Node left;
		private Node right;

		public Node(Comparable data){
			this.data = data;
		}

		public void addNode(Node newNode){
			if(newNode.data.compareTo(this.data) < 0){//left tree
				if(this.left == null){
					this.left = newNode;
				}else{
					this.left.addNode(newNode);
				}
			}else if(newNode.data.compareTo(this.data) > 0){//right tree
				if(this.right == null){
					this.right = newNode;
				}else{
					this.right.addNode(newNode);
				}
				
			}
		}

		public void printNode(){
			if(this.left != null){
				this.left.printNode();
			}
			System.out.println(this.data);
			if(this.right != null){
				this.right.printNode();
			}
		}
	}

	private Node root = null;

	public void add(Comparable data){
		Node newNode = new Node(data);

		if(root == null){
			root = newNode;
		}else{
			root.addNode(newNode);
		}
	}

	public void print(){
		this.root.printNode();
	}
}

在主函数中添加节点,并进行中序遍历输出:

BinarrySortTree tree = new BinarrySortTree();
for(int i=0;i<5;i++){
	tree.add(5-i);
}
tree.print();

类外部定义比较器

还有一种支持对象比较的方式,属于一种补救方式,比如在大型项目中,我所设计的类已经定义打包好了,不希望再对它进行任何修改,现在为了要使此类具有可比较特性,还有一种补救方法就是在类外部定义一个特定的比较器,降低类的耦合度。但此种方法不推荐使用。

class Worker{
	private String name = null;
	private int age = 0;
	private double salary = 0.0;

	public String getName() {
	    return this.name;
	}

	public int getAge() {
	    return this.age;
	}

	public double getSalary() {
	    return this.salary;
	}

	public void setName(String name) {
	    this.name = name;
	}

	public void setAge(int age) {
	    this.age = age;
	}

	public void setSalary(double salary) {
	    this.salary = salary;
	}

	public Worker(){

	}

	public Worker(String name, int age, double salary){
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	public boolean equals(Object o){
		if(o == null)
			return false;
		if(!(o instanceof Worker))
			return false;
		Worker worker = (Worker)o;
		if(this.name.equals(worker.name) && this.age == worker.age 
		&& this.salary == worker.salary)
			return true;
		else
			return false;
	}

	public String toString(){
		return "name=" + this.name + " age=" + this.age + " salary=" + this.salary;
	}
}

定义外在的WorkerComparator比较器 ,需要实现该接口的equals和compare方法,equals一般在要支持比较的那个类内部定义完善。使用上述Comparator比较器。

class WorkerComparator implements Comparator<Worker>{
	public boolean equals(Object obj){
		return this.equals(obj);
	}

	public int compare(Worker o1, Worker o2){
		if(o1.getSalary() < o2.getSalary()){
			return -1;
		}else if(o1.getSalary() > o2.getSalary()){
			return 1;
		}else{
			return 0;
		}
	}
}

程序输出如下:

before sort: name=peter age=30 salary=12000.0 name=tony age=25 salary=15000.0 name=paul age=31 salary=13000.0 name=danel age=35 salary=25000.0 after sort: name=peter age=30 salary=12000.0 name=paul age=31 salary=13000.0 name=tony age=25 salary=15000.0 name=danel age=35 salary=25000.0

说明这种方式也支持对象数组的比较,但还是推荐大家使用定义类的同时,如果觉得此类有必要支持比较,就实现Comparable接口,毕竟Comparator是作为一种补救出现。

两种比较方式的区别

从网上看到的一些关于这两个接口的比较: