Java基础之 Comparable和Comparator的区别

 

在网上看了很多关于Comparable 和 Comparator的区别 感觉最清晰的一篇文章是五月的仓颉这位大牛总结的博客。结合自身代码总算懂了一点。但是还是遇到了一个问题。直到亲自解决掉这个问题,这两个接口的区别我才算真正搞清楚。

先从概念出发。将大牛总结的博客搬过来

Comparable和Comparator的区别

前言

初次碰到这个问题是之前有一次电话面试,问了一个小时的问题,其中有一个问题就问到Comparable和Comparator的区别,当时没答出来。之后是公司入职时候做的一套Java编程题,里面用JUnit跑用例的时候也用到了Comparator接口,再加上JDK的大量的类包括常见的String、Byte、Char、Date等都实现了Comparable接口,因此要学习一下这两个类的区别以及用法。

Comparable

Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:

1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数

2、比较者等于被比较者,那么返回0

3、比较者小于被比较者,那么返回负整数

写个很简单的例子:

public class Domain implements Comparable<Domain>
{
    private String str;

    public Domain(String str)
    {
        this.str = str;
    }

    public int compareTo(Domain domain)
    {
        if (this.str.compareTo(domain.str) > 0)
            return 1;
        else if (this.str.compareTo(domain.str) == 0)
            return 0;
        else 
            return -1;
    }
    
    public String getStr()
    {
        return str;
    }
}
public static void main(String[] args)
    {
        Domain d1 = new Domain("c");
        Domain d2 = new Domain("c");
        Domain d3 = new Domain("b");
        Domain d4 = new Domain("d");
        System.out.println(d1.compareTo(d2));
        System.out.println(d1.compareTo(d3));
        System.out.println(d1.compareTo(d4));
    }
}
运行结果为:
0
1
-1

注意一下,前面说实现Comparable接口的类是可以支持和自己比较的,但是其实代码里面Comparable的泛型未必就一定要是Domain,将泛型指定为String或者指定为其他任何任何类型都可以----只要开发者指定了具体的比较算法就行。

Comparator

Comparator可以认为是是一个外比较器,个人认为有两种情况可以使用实现Comparator接口的方式:

1、一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较

2、一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式

Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:

1、o1大于o2,返回正整数

2、o1等于o2,返回0

3、o1小于o3,返回负整数

写个很简单的例子,上面代码的Domain不变(假设这就是第2种场景,我对这个compareTo算法实现不满意,要自己写实现):

public class DomainComparator implements Comparator<Domain>
{
    public int compare(Domain domain1, Domain domain2)
    {
        if (domain1.getStr().compareTo(domain2.getStr()) > 0)
            return 1;
        else if (domain1.getStr().compareTo(domain2.getStr()) == 0)
            return 0;
        else 
            return -1;
    }
}
public static void main(String[] args)
{
    Domain d1 = new Domain("c");
    Domain d2 = new Domain("c");
    Domain d3 = new Domain("b");
    Domain d4 = new Domain("d");
    DomainComparator dc = new DomainComparator();
    System.out.println(dc.compare(d1, d2));
    System.out.println(dc.compare(d1, d3));
    System.out.println(dc.compare(d1, d4));
}

看一下运行结果:

0
1
-1

当然因为泛型指定死了,所以实现Comparator接口的实现类只能是两个相同的对象(不能一个Domain、一个String)进行比较了,因此实现Comparator接口的实现类一般都会以"待比较的实体类+Comparator"来命名

总结

总结一下,两种比较器Comparable和Comparator,后者相比前者有如下优点:

1、如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那么可以实现Comparator接口,自定义一个比较器,写比较算法

2、实现Comparable接口的方式比实现Comparator接口的耦合性要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。从这个角度说,其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。实际上实现Comparator接口的方式后面会写到就是一种典型的策略模式

当然,这不是鼓励用Comparator,意思是开发者还是要在具体场景下选择最合适的那种比较器而已。

原文地址:http://www.cnblogs.com/xrq730/p/4850140.html

---------------------------------------------------------------以下是我自己的总结--------------------------------------------------------------------------

java.lang
接口 Comparable<T>

java.util
接口 Comparator<T>

一开始遇到这两个接口的时候,是在学习Collection接口中的 TreeSetf集合,这个集合因为在add的时候回自动排序,就造成了取出的时候是有序的,所以该集合需要存入的对象需要实现排序的功能。两个方面可以去做到。

第一种(Comparable).

TreeSet集合中add()方法传入的对象 自身具有比较的功能例如String类(String类也是实现了 Comparable接口)或者自定义的类(例如我的测试代码中自定义的Student类)需要具有比较的功能,也就是需要实现Comparable接口。

 第二种方法(Comparator)

TreeSet集合在创建的时候  需要将比较器(Comparator)作为参数传入到 TresSet的构造方法中。其中TreeSet的一个构造方法如下

TreeSet(Comparator<? super E> comparator)
          构造一个新的空 TreeSet,它根据指定比较器进行排序。

这个比较器需要开发者自己定义,要么专门定义一个类 去实现Comparator<T>接口   该类就是一个外部比较器  该类具有了比较T类型对象的功能;要门在TreeSet构造方法中 定义一个匿名内部类new Comparator(){};    该匿名内部类也需要实现compare方法  将该匿名内部类作为参数传入到TreeSet中 ,使TreeSet集合中 可以用这个外部给定的比较器去 比较对象。 

代码1   本类已经具有自比较功能(本类实现了Comparable接口

public class Student implements Comparable<Student>{

	private String name;
	
	public Student(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

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



	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}


	@Override
	public int compareTo(Student o) {
		// TODO Auto-generated method stub
		int res = name.compareTo(o.name);
		return res;
	}
}





public class TestCollection {

	static Student[] stu;

	public static void main(String[] args) {

		testTreeSet();
		}


	/**
	 * TreeSet: 底层使用 TreeMap的树形结构 该类 要求使用TreeSet的对象的类 去实现Comparable<T>接口 特征: 无序 不可重复
	 * 值不能为null 按照自然顺序输出 或者根据创建时映射的Comparator排序
	 */
	private static void testTreeSet() {
		Student stu[] = initStu();
		TreeSet<Student> treeSet = new TreeSet<Student>();// 创建一个TreeSet集合
	
		for (int i = 0; i < stu.length; i++) {
			treeSet.add(stu[i]);
		}
		System.out.println("********************TreeSet迭代器输出测试****************");
		Iterator<Student> dit = treeSet.iterator();
		while (dit.hasNext()) {
			System.out.println(dit.next());
		}
		System.out.println("****************************************************** \n");

	}

	private static Student[] initStu() {

		Student s1 = new Student("zs");
		Student s2 = new Student("ls");
		Student s3 = new Student("zmj");
		Student s4 = new Student("zh");
		Student s5 = new Student("hst");
		Student[] stu = { s1, s2, s3, s4, s5};
		
		//HashMap<Integer, String> map = new HashMap<Integer, String>();
		
		return stu;
	}

}

程序输出结果:

********************TreeSet迭代器输出测试****************
Student [name=hst]
Student [name=ls]
Student [name=zh]
Student [name=zmj]
Student [name=zs]
****************************************************** 

代码 2.1  使用Comparator接口  创建一个外部比较器类。

//自己创建的一个外部比较器  只能比较Student对象,
//如果想比较 Student和Teacher 两个不同的类的对象  则需要将这两个类抽象出来一个父类Person 这里的Comparator泛型传入父类Person
public class Comp implements Comparator<Student> {

	@Override
	public int compare(Student o1, Student o2) {
		// TODO Auto-generated method stub
		return (-1)*o1.getName().compareTo(o2.getName());
	}

}


public class Student{

	private String name;
	
	public Student(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

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



	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
}




public class TestCollection {

	static Student[] stu;

	public static void main(String[] args) {

		testTreeSet();	

		}
	

	
	/**
	 * TreeSet: 底层使用 TreeMap的树形结构 该类 要求使用TreeSet的对象的类 去实现Comparable<T>接口 特征: 无序 不可重复
	 * 值不能为null 按照自然顺序输出 或者根据创建时映射的Comparator排序
	 */
	private static void testTreeSet() {
		Student stu[] = initStu();
		TreeSet<Student> treeSet = new TreeSet<Student>(new Comp());//将外部构造器对象作为参数传入TreeSet构造方法中
		
		for (int i = 0; i < stu.length; i++) {
			treeSet.add(stu[i]);
		}
		System.out.println("********************TreeSet迭代器输出测试****************");
		Iterator<Student> dit = treeSet.iterator();
		while (dit.hasNext()) {
			System.out.println(dit.next());
		}
		System.out.println("****************************************************** \n");

	}

	private static Student[] initStu() {

		Student s1 = new Student("zs");
		Student s2 = new Student("ls");
		Student s3 = new Student("zmj");
		Student s4 = new Student("zh");
		Student s5 = new Student("hst");
		Student[] stu = { s1, s2, s3, s4, s5};
		
		//HashMap<Integer, String> map = new HashMap<Integer, String>();
		
		return stu;
	}

}

程序输出结果:

********************TreeSet迭代器输出测试****************
Student [name=zs]
Student [name=zmj]
Student [name=zh]
Student [name=ls]
Student [name=hst]

****************************************************** 

代码2.2   在创建TreeSet集合的时候   使用匿名内部类实现Comparator接口  创建比较器


public class Student{

	private String name;
	
	public Student(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

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



	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
}






public class TestCollection {

	static Student[] stu;

	public static void main(String[] args) {

		testTreeSet();
	
		}
	
	
	/**
	 * TreeSet: 底层使用 TreeMap的树形结构 该类 要求使用TreeSet的对象的类 去实现Comparable<T>接口 特征: 无序 不可重复
	 * 值不能为null 按照自然顺序输出 或者根据创建时映射的Comparator排序
	 */
	private static void testTreeSet() {
		Student stu[] = initStu();
		/**
		 * 如果Student对象没有实现Comparable接口,
		 * 那么在TreeSet的构造方法中传入 实现了 接口Comparator 的对象 或者匿名内部类
		 */
		TreeSet<Student> treeSet = new TreeSet<Student>(new Comparator<Student>() {

			@Override
			public int compare(Student o1, Student o2) {
				// TODO Auto-generated method stub
				return o1.getName().compareTo(o2.getName());
			}

		});

		for (int i = 0; i < stu.length; i++) {
			treeSet.add(stu[i]);
		}
		System.out.println("********************TreeSet迭代器输出测试****************");
		Iterator<Student> dit = treeSet.iterator();
		while (dit.hasNext()) {
			System.out.println(dit.next());
		}
		System.out.println("****************************************************** \n");

	}

	private static Student[] initStu() {

		Student s1 = new Student("zs");
		Student s2 = new Student("ls");
		Student s3 = new Student("zmj");
		Student s4 = new Student("zh");
		Student s5 = new Student("hst");
		Student[] stu = { s1, s2, s3, s4, s5};
		
		//HashMap<Integer, String> map = new HashMap<Integer, String>();
		
		return stu;
	}

}

程序输出结果:

********************TreeSet迭代器输出测试****************
Student [name=hst]
Student [name=ls]
Student [name=zh]
Student [name=zmj]
Student [name=zs]
**************************************************************

本人犯的错误,将Student对象实现了Comparator接口,但是在创建TreeSet集合时并没有将其作为参数传入集合中。自认为该对象就实现了可自比较的功能,其实不然 该对象只是一个外部比较器,只是可以比较两个Student对象而已,  换句比较绕弯子的话来讲就是    Student对象可以比较两个Student对象的大小。。。。   Student对象并不能自身与另一个Student对象比较。从Comparable接口中的 compare方法  与 Comparator接口中的compareTo 方法就可以看出不同。  compare方法是自比较,参数只有一个;compareTo方法是比较器的比较两个对象大小,参数为两个;

代码如下

public class Student implements Comparator<Student> {

	private String name;

	public Student(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;
	}

	public String getName() {
		return name;
	}

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

	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}


	/**
	 * 对应Comparator接口
	 */

	@Override
	public int compare(Student o1, Student o2) {
		// TODO Auto-generated method stub
		return (o1.getName().compareTo(o2.getName()));
	}

}


public class TestCollection {

	static Student[] stu;

	public static void main(String[] args) {

		testTreeSet();
	
		}
	
	
	/**
	 * TreeSet: 底层使用 TreeMap的树形结构 该类 要求使用TreeSet的对象的类 去实现Comparable<T>接口 特征: 无序 不可重复
	 * 值不能为null 按照自然顺序输出 或者根据创建时映射的Comparator排序
	 */
	private static void testTreeSet() {
		Student stu[] = initStu();
        TreeSet<Student> treeSet = new TreeSet<Student>();

		for (int i = 0; i < stu.length; i++) {
			treeSet.add(stu[i]);
		}
		System.out.println("********************TreeSet迭代器输出测试****************");
		Iterator<Student> dit = treeSet.iterator();
		while (dit.hasNext()) {
			System.out.println(dit.next());
		}
		System.out.println("****************************************************** \n");

	}

	private static Student[] initStu() {

		Student s1 = new Student("zs");
		Student s2 = new Student("ls");
		Student s3 = new Student("zmj");
		Student s4 = new Student("zh");
		Student s5 = new Student("hst");
		Student[] stu = { s1, s2, s3, s4, s5};
		
		
		return stu;
	}

}


程序运行结果如下

Exception in thread "main" java.lang.ClassCastException: Test412HashMap.Student cannot be cast to java.lang.Comparable
    at java.util.TreeMap.compare(Unknown Source)
    at java.util.TreeMap.put(Unknown Source)
    at java.util.TreeSet.add(Unknown Source)
    at Test412HashMap.TestCollection.testTreeSet(TestCollection.java:93)
    at Test412HashMap.TestCollection.main(TestCollection.java:27)
 

总结:

个人认为 Comparator功能相比较Comparable 更加强大一些, 它可以改变类本身的排序机制,强行按照实现的外部构造器去进行排序,例如代码2.1中的输出结果顺序是相反的,有的人可能会说 是因为Student类没有实现Comparable接口规定比较方式。但结果是  依旧会被外部构造器的排序机制覆盖。依旧为Comparator为主。

也正如上文大牛中所说,实现Comparable接口的方式比实现Comparator接口的耦合性要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。从这个角度说,其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。实际上实现Comparator接口的方式后面会写到就是一种典型的策略模式

猜你喜欢

转载自blog.csdn.net/Hurricane_m/article/details/89327546