在网上看了很多关于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接口的方式后面会写到就是一种典型的策略模式。