1 使用场景
在 Java 中的对象,一般情况下无法比较对象的大小。但业务中经常会涉及到对象数组的排序问题,也就涉及到了对象之间的比较问题。
Java 中实现对象排序的方式有两种:
1、对象实现
java.lang.Comparable
接口,重写compareTo(Object obj)
方法,通过方法返回值来比较大小;实现java.lang.Comparable
接口的对象列表或数组可以通过Collections.sort(List<T> list)
或Arrays.sort(Object[] arr)
方法进行自动排序;2、使用
java.util.Comparator
对象,重写compare(Object o1, Object o2)
方法,通过方法返回值比较大小;可以将Comparator
对象传递给Collections.sort(List<T> list, Comparator c)
或Arrays.sort(Object[] arr, Comparator c)
方法实现排序控制。
2 Java 比较器的两种实现方式
2.1 Comparable 接口
2.1.1 概述
实现Comparable
接口的类必须重写compareTo(Object obj)
方法,两个对象通过compareTo(Object obj)
方法的返回值比较大小:
- 例如:obj.compareTo(obj2)
如果 obj 大于 obj2,则返回正整数;
如果 obj 小于 obj2,则返回负整数;
如果 obj 等于 obj2,则返回0。
实现Comparable
接口的类对象列表或数组可以直接通过Collections.sort(List<T> list)
或Arrays.sort(Object[] arr)
方法进行自动排序,无需指定比较器。这种方式称为自然排序,也可以理解为内部比较器。
2.1.2 代码示例
public class Demo01 {
public static void main(String[] args) {
List<People> list = new ArrayList<>();
People[] arr = new People[4];
// 添加测试数据
People p1 = new People("小黑", 22);
list.add(p1);
arr[0] = p1;
People p2 = new People("小白", 19);
list.add(p2);
arr[1] = p2;
People p3 = new People("小蓝", 20);
list.add(p3);
arr[2] = p3;
People p4 = new People("小绿", 18);
list.add(p4);
arr[3] = p4;
System.out.println("列表 - 自然排序,排序前:");
list.forEach(System.out::println);
// 自然排序
Collections.sort(list);
System.out.println("列表 - 自然排序,排序后:");
list.forEach(System.out::println);
System.out.println("---------------------------");
System.out.println("数组 - 自然排序,排序前:");
System.out.println(Arrays.toString(arr));
// 自然排序
Arrays.sort(arr);
System.out.println("数组 - 自然排序,排序后:");
System.out.println(Arrays.toString(arr));
}
}
class People implements Comparable<People> {
private String name;
private Integer age;
public People(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(People people) {
if (this.age > people.age) {
return 1;
} else if (this.age < people.age) {
return -1;
} else {
return 0;
}
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果
列表 - 自然排序,排序前:
People{name='小黑', age=22}
People{name='小白', age=19}
People{name='小蓝', age=20}
People{name='小绿', age=18}
列表 - 自然排序,排序后:
People{name='小绿', age=18}
People{name='小白', age=19}
People{name='小蓝', age=20}
People{name='小黑', age=22}
---------------------------
数组 - 自然排序,排序前:
[People{name='小黑', age=22}, People{name='小白', age=19}, People{name='小蓝', age=20}, People{name='小绿', age=18}]
数组 - 自然排序,排序后:
[People{name='小绿', age=18}, People{name='小白', age=19}, People{name='小蓝', age=20}, People{name='小黑', age=22}]
Process finished with exit code 0
2.1.3 附部分源码
2.1.3.1 Comparable 接口源码
public interface Comparable<T> {
public int compareTo(T o);
}
2.1.3.2 Integer 实现 Comparable 接口的自然排序逻辑源码
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
2.1.3.3 String 实现 Comparable 接口的自然排序逻辑源码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/**
* 该方法的比较结果其实就是两个字符串的首个不同的字符之间的 ASCII 编码的差值,如果其中一个字符串完全匹配另一个字符串,则返回两个字符串的长度差
*/
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
// 逐个字符进行比较
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
// 当字符不同时,返回结果为两个字符的 ASCII 编码的差值
// 例如:apple 和 application
// 从头开始逐个字符匹配,匹配到第5位字符 e 和 i 不同;
// ASCII 编码中字符 e 的十进制值为 101,字符 i 的十进制值为 105;
// 此时返回结果为 101 - 105 = -4
return c1 - c2;
}
k++;
}
// 走到这里说明两个字符串中,一个字符串是另一个字符串从头开始完全匹配的子串,此时返回结果为两个字符串的长度差
// 例如:apple 和 app,返回结果为 5 - 2 = 3
return len1 - len2;
}
}
2.2 Comparator 接口
2.2.1 概述
当对象类没有实现Comparable
接口,或者实现了Comparable
接口但自然排序规则不适用于当前操作时,可以使用Comparator
接口,重写compare(Object o1, Object o2)
方法进行比较:
- 例如:compare(o1, o2)
如果返回正整数,则表示 o1 大于 o2;
如果返回负整数,则表示 o1 小于 o2;
如果返回0,则表示 o1 等于 o2。
将Comparator
对象传递给Collections.sort(List<T> list, Comparator c)
或Arrays.sort(Object[] arr, Comparator c)
方法即可实现排序控制。这种方式称为定制排序,也可以理解为外部比较器。
2.2.2 代码示例
public class Demo02 {
public static void main(String[] args) {
List<PeopleNew> list = new ArrayList<>();
PeopleNew[] arr = new PeopleNew[4];
PeopleNew p1 = new PeopleNew("小黑", 22);
list.add(p1);
arr[0] = p1;
PeopleNew p2 = new PeopleNew("小白", 19);
list.add(p2);
arr[1] = p2;
PeopleNew p3 = new PeopleNew("小蓝", 20);
list.add(p3);
arr[2] = p3;
PeopleNew p4 = new PeopleNew("小绿", 18);
list.add(p4);
arr[3] = p4;
// 外部比较器定制排序
Comparator<PeopleNew> comparator = new Comparator<PeopleNew>() {
@Override
public int compare(PeopleNew o1, PeopleNew o2) {
if (o1.getAge() > o2.getAge()) {
return 1;
} else if (o1.getAge() < o2.getAge()) {
return -1;
} else {
return 0;
}
}
};
// lambda 写法
// Comparator<PeopleNew> comparator = (o1, o2) -> {
// if (o1.getAge() > o2.getAge()) {
// return 1;
// } else if (o1.getAge() < o2.getAge()) {
// return -1;
// } else {
// return 0;
// }
// };
System.out.println("列表 - 定制排序,排序前:");
list.forEach(System.out::println);
// 定制排序
Collections.sort(list, comparator);
// 或者直接使用 List 的 sort(java.util.Comparator<? super E> c) 方法
// list.sort(comparator);
// 使用 内部类 + lambda 表达式 一条语句搞定
// list.sort((o1, o2) -> {
// if (o1.getAge() > o2.getAge()) {
// return 1;
// } else if (o1.getAge() < o2.getAge()) {
// return -1;
// } else {
// return 0;
// }
// });
System.out.println("列表 - 定制排序,排序后:");
list.forEach(System.out::println);
list.sort(comparator.reversed());
System.out.println("列表 - 定制排序,倒序排序:");
list.forEach(System.out::println);
System.out.println("---------------------------");
System.out.println("数组 - 定制排序,排序前:");
System.out.println(Arrays.toString(arr));
// 定制排序
Arrays.sort(arr, comparator);
System.out.println("数组 - 定制排序,排序后:");
System.out.println(Arrays.toString(arr));
Arrays.sort(arr, comparator.reversed());
System.out.println("数组 - 定制排序,倒序排序:");
System.out.println(Arrays.toString(arr));
}
}
class PeopleNew {
private String name;
private Integer age;
public PeopleNew(String name, Integer age) {
this.name = name;
this.age = age;
}
public Integer getAge() {
return age;
}
@Override
public String toString() {
return "PeopleNew{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果
列表 - 定制排序,排序前:
PeopleNew{name='小黑', age=22}
PeopleNew{name='小白', age=19}
PeopleNew{name='小蓝', age=20}
PeopleNew{name='小绿', age=18}
列表 - 定制排序,排序后:
PeopleNew{name='小绿', age=18}
PeopleNew{name='小白', age=19}
PeopleNew{name='小蓝', age=20}
PeopleNew{name='小黑', age=22}
列表 - 定制排序,倒序排序:
PeopleNew{name='小黑', age=22}
PeopleNew{name='小蓝', age=20}
PeopleNew{name='小白', age=19}
PeopleNew{name='小绿', age=18}
---------------------------
数组 - 定制排序,排序前:
[PeopleNew{name='小黑', age=22}, PeopleNew{name='小白', age=19}, PeopleNew{name='小蓝', age=20}, PeopleNew{name='小绿', age=18}]
数组 - 定制排序,排序后:
[PeopleNew{name='小绿', age=18}, PeopleNew{name='小白', age=19}, PeopleNew{name='小蓝', age=20}, PeopleNew{name='小黑', age=22}]
数组 - 定制排序,倒序排序:
[PeopleNew{name='小黑', age=22}, PeopleNew{name='小蓝', age=20}, PeopleNew{name='小白', age=19}, PeopleNew{name='小绿', age=18}]
Process finished with exit code 0
2.2.3 附部分源码
Comparator 接口定制排序的方法源码
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
/**
* 倒序排序
*/
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
}
注:Comparator 接口的方法有很多,此处只讨论排序相关的方法,故不赘述其他方法源码。如需追溯倒序排序底层实现,源码见下方Collections 接口部分源码
。
Collections 接口部分源码
public class Collections {
public static <T> Comparator<T> reverseOrder() {
return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
}
public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
if (cmp == null)
return reverseOrder();
if (cmp instanceof ReverseComparator2)
return ((ReverseComparator2<T>)cmp).cmp;
return new ReverseComparator2<>(cmp);
}
private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable {
private static final long serialVersionUID = 7207038068494060240L;
static final ReverseComparator REVERSE_ORDER = new ReverseComparator();
public int compare(Comparable<Object> c1, Comparable<Object> c2) {
return c2.compareTo(c1);
}
private Object readResolve() { return Collections.reverseOrder(); }
@Override
public Comparator<Comparable<Object>> reversed() {
return Comparator.naturalOrder();
}
}
private static class ReverseComparator2<T> implements Comparator<T>, Serializable {
private static final long serialVersionUID = 4374092139857L;
/**
* The comparator specified in the static factory. This will never
* be null, as the static factory returns a ReverseComparator
* instance if its argument is null.
*
* @serial
*/
final Comparator<T> cmp;
ReverseComparator2(Comparator<T> cmp) {
assert cmp != null;
this.cmp = cmp;
}
public int compare(T t1, T t2) {
// 倒序
return cmp.compare(t2, t1);
}
public boolean equals(Object o) {
return (o == this) ||
(o instanceof ReverseComparator2 &&
cmp.equals(((ReverseComparator2)o).cmp));
}
public int hashCode() {
return cmp.hashCode() ^ Integer.MIN_VALUE;
}
@Override
public Comparator<T> reversed() {
return cmp;
}
}
}
3 Java 比较器两种实现方式的区别
1、接口所在包不同:Comparable
接口位于java.lang
包下;而Comparator
接口位于java.util
包下;
2、定义比较逻辑位置不同:Comparable
接口是类继承接口后在类的内部定义比较逻辑;而Comparator
接口是在类的外部定义比较逻辑;
3、接口方法及参数不同:Comparable
接口只有一个compareTo(Object obj)
方法,使用时拿当前对象与形参对象进行比较;而Comparator
接口包含多个方法,其中提供compare(Object o1, Object o2)
比较方法,传入两个形参对象进行比较。