目录
一、Comparable 自然排序
二、Comparator 定制排序
三、Comparable和Comparator的区别
四、总结
Java 中为我们提供了两种比较机制:Comparable 和 Comparator,他们之间有什么区别呢?今天来了解一下。
一、Comparable 自然排序
Comparable 在 java.lang 包下,是一个接口,内部只有一个方法 compareTo()。
public interface Comparable<T> {public int compareTo(T o);
}
Comparable是一个排序接口,它强制实现排序规则在类本身定义。Comparable 可以让实现它的类的对象进行比较,具体的比较规则是按照 compareTo 方法中的规则进行。这种顺序称为自然顺序。也就是说,如果一个类实现了Comparable接口,那么该类的对象就可以进行自然排序。
实现Comparable需要重写其中的compareTo()方法,该方法的返回值类型为int,根据比较结果返回正整数、0或者负整数。
compareTo 方法的返回值有三种情况:
- e1.compareTo(e2) > 0 即 e1 > e2。返回正整数:当前对象大于目标对象。
- e1.compareTo(e2) = 0 即 e1 = e2。返回0:当前对象等于目标对象。
- e1.compareTo(e2) < 0 即 e1 < e2。返回负整数:当前对象小于目标对象。
注意:
1)由于 null 不是一个类,也不是一个对象,因此在重写 compareTo 方法时应该注意 e.compareTo(null) 的情况,即使 e.equals(null) 返回 false,compareTo 方法也应该主动抛出一个空指针异常 NullPointerException。
2)Comparable 实现类重写 compareTo 方法时一般要求 e1.compareTo(e2) == 0 的结果要和 e1.equals(e2) 一致。这样将来使用 SortedSet 等根据类的自然排序进行排序的集合容器时可以保证保存的数据的顺序和想象中一致。
有人可能好奇上面的第二点如果违反了会怎样呢?
举个例子,如果你往一个 SortedSet 中先后添加两个对象 a 和 b,a b 满足 (!a.equals(b) && a.compareTo(b) == 0),同时也没有另外指定 Comparator。那当你添加完 a 再添加 b 时会添加失败返回 false, SortedSet 的 size 也不会增加。因为在 SortedSet 看来它们是相同的,而 SortedSet 中是不允许重复的。
实际上所有实现了 Comparable 接口的 Java 核心类的结果都和 equlas 方法保持一致。 实现了 Comparable 接口的 List 或则数组可以使用Collections.sort() 或者 Arrays.sort() 方法进行排序。
实现了 Comparable 接口的对象才能够直接被用作 SortedMap (SortedSet) 的 key,要不然得在外边指定 Comparator 排序规则。
因此自己定义的类如果想要使用有序的集合类,需要实现 Comparable 接口。
以下代码示例展示了如何实现Comparable接口:
public class Person implements Comparable<Person> {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Person o) {return this.age - o.age;}// 省略getter/setter方法
}List<Person> personList = new ArrayList<>();
personList.add(new Person("张三", 20));
personList.add(new Person("李四", 18));
personList.add(new Person("王五", 21));
Collections.sort(personList);
System.out.println(personList);
在上述代码中,Person类实现了Comparable接口,并重写了其中的compareTo()方法,按照年龄大小进行比较。这里的比较规则是年龄越小,优先级越高。
使用Comparable进行排序时,只需要对集合调用Collections.sort()方法,因为该方法已经内置了对自然排序的支持。
运行上述代码,结果如下所示:
[Person{name='李四', age=18}, Person{name='张三', age=20}, Person{name='王五', age=21}]
二、Comparator 定制排序
Comparator 在 java.util 包下,也是一个接口,JDK 1.8 以前只有两个方法:
public interface Comparator<T> {public int compare(T lhs, T rhs);public boolean equals(Object object);
}
JDK 1.8 以后又新增了很多方法:
基本上都是跟 Function 相关的,这里暂不介绍 1.8 新增的。
从上面内容可知使用自然排序需要类实现 Comparable,并且在内部重写 comparaTo() 方法。而 Comparator 则是在外部制定排序规则,然后作为排序策略参数传递给某些类,比如 Collections.sort(), Arrays.sort(), 或者一些内部有序的集合(比如 SortedSet,SortedMap 等)。
Comparator是一个比较器接口,它可以为需要排序的类提供多种不同的排序规则。与Comparable不同的是,Comparator实现的排序规则是在调用时才确定。
使用方式主要分三步:
- 创建一个 Comparator 接口的实现类,并赋值给一个对象。在 compare 方法中针对自定义类写排序规则。
- 需要重写其中的compare()方法,在方法中传入两个待比较的对象,并返回比较结果,含义和compareTo()方法相同。
- 但是需要注意的是,对于同一类型的不同对象,不同的比较器实现可能会返回不同的比较结果。
- 将 Comparator 对象作为参数传递给排序类的某个方法。
- 向排序类中添加 compare() 方法中使用的自定义类。
以下代码示例展示了如何使用Comparator进行排序:
public class PersonAgeComparator implements Comparator<Person> {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge() - o2.getAge();}
}List<Person> personList = new ArrayList<>();
personList.add(new Person("张三", 20));
personList.add(new Person("李四", 18));
personList.add(new Person("王五", 21));
Collections.sort(personList, new PersonAgeComparator());
System.out.println(personList);
在上述代码中,我们实现了一个PersonAgeComparator
比较器,按照年龄大小进行比较。在调用Collections.sort()
方法时,传入该比较器作为参数,即可按照指定的规则进行排序。
其实可以看到,Comparator 的使用是一种策略模式。
运行上述代码,结果如下所示:
[Person{name='李四', age=18}, Person{name='张三', age=20}, Person{name='王五', age=21}]
除此之外,我们还可以使用lambda表达式来简化比较器的实现:
Collections.sort(personList, (o1, o2) -> o1.getAge() - o2.getAge());
三、Comparable和Comparator的区别
Java中的Comparable和Comparator都是用于集合排序的接口,但它们有明显的区别。
-
Comparable是内部比较器,而Comparator是外部比较器。实现Comparable的类,在其内部定义了比较规则;而使用Comparator时,则需要单独定义一个外部比较器。
-
Comparable的排序规则是固定的,不可更改;而Comparator的排序规则是可以根据需要自己定义的。
-
Comparable的排序规则只适用于该类的对象,而Comparator的排序规则可以适用于不同类型的对象。
-
当我们需要对自定义类型进行排序时,如果实现了Comparable接口,就意味着该类型具有了默认的排序规则;如果没有实现Comparable接口,则需要单独定义Comparator。
对于这个问题,没有一定的规则来决定选择哪一个更好,因为它取决于具体情况以及开发者的个人偏好。
- 如果你所需要排序的类已经实现了Comparable接口,那么你可以直接使用该类默认的比较规则进行排序。否则,你可以考虑实现一个或多个Comparator接口,并基于不同的比较规则对同一个类进行排序。
- 在某些情况下,你可能会发现自己需要对同一个类使用多个不同的比较规则进行排序。在这种情况下,使用Comparator接口是一种更加灵活的选择,因为你可以根据需要编写任意数量的比较器,并将它们与同一个类的不同实例一起使用。
- 在另一些情况下,你可能会发现自然排序已经足够满足你的需求。如果你只需要对一个类中的对象按照默认的比较规则进行排序,那么你可以直接实现Comparable接口。
在Java中,Comparable和Comparator都是非常有用的接口,能够帮助开发者轻松地对集合进行排序,并根据特定的比较规则来确定排序顺序。具体选择哪一个接口,需要考虑多方面的因素,并且也要结合具体的需求和实际情况来进行选择。
总的来说,Comparable和Comparator都是Java中用于集合排序的接口,但它们有各自的优缺点。在实际编程中,我们需要根据具体情况选择使用哪种方式,才能更加高效地完成任务。
四、总结
Java 中的两种排序方式:
- Comparable 自然排序。(实体类实现)
- Comparator 是定制排序。(无法修改实体类时,直接在调用方创建)
同时存在时,采用 Comparator(定制排序)的规则进行比较。
对于一些普通的数据类型(比如 String, Integer, Double…),它们默认实现了Comparable 接口,实现了 compareTo 方法,我们可以直接使用。
而对于一些自定义类,它们可能在不同情况下需要实现不同的比较策略,我们可以新创建 Comparator 接口,然后使用特定的 Comparator 实现进行比较。
这就是 Comparable 和 Comparator 的区别。