排序的介绍
为什么需要排序
在开发中,需要对一组对象进行排序,是非常常见的业务需求。排序可以让数据更加有序,便于查找和操作,以及数据的统计和分析。
在Java中,可以使用Arrays类对数组提供的方法进行排序,或使用Collections类对集合进行排序
可以通过Comparable自然排序和Comparator定制排序进行实现
使用Arrays类对数组进行排序
// 没传入compartor比较器,内部会使用comparable比较器
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a); // 如果comparator是空的,就调用上面的sort方法
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
通过源码发现,我们想要对Object进行排序,可以使用java提供的上面两个接口,一个是直接传入对象数组,另一个是传入对象数组和Comparator比较器。
- 直接传入对象数组的方式为使用自然排序
- 传入Comparator比较器使用的是定制排序
- 如果类中既实现了comparable,在调用sort时又传入comparator比较器,会优先使用comparator定制排序。
如果不传入Comparator比较器,就会使用comparable比较器进行排序,此时就会看你是否实现了comparable比较器,如果没实现comparable接口,就会出现类型转换异常。因为在源码中会将实体类转换为comparable,具体可以自行查看源码深入探究。
使用Collections类对集合进行排序
源码如下
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
通过源码可以发现Collections.sort 提供了两个接口,一个是直接传我们想要排序的List,另一个是传传我们想要排序的List和Comparator比较器。
再继续深入代码,发现Collections排序的底层是基于Arrays的
为什么叫自然排序和定制排序
自然排序
自然排序(Natural Ordering)也叫常规排序,是指使用Java对象默认的排序规则进行排序,例如整数默认按照数值大小升序排序,字符串默认按照字典序升序排序。
Comparable接口则提供了一种自然排序的方式,它只定义了一种比较规则,且只能在同一个对象之间进行排序,不能定制化排序。说白了就是不能玩花活。
定制排序
定制排序(Customized Ordering)是指使用自己定制的排序规则进行排序,它比较灵活,例如按照字符串长度升序排序,或者按照对象某个属性的大小排序。
Comparator接口提供了一种定制排序的方式,它可以定义多种不同的排序规则,程序员可以根据需要选择不同的比较器实现类。
使用Comparable自然排序
使用自然排序,就是在类中实现comparable接口,然后通过Arrays.sort(T[] arr) 或 Collections.sort(List list)的方式进行排序,需要注意的是,如果类中没实现comparable接口,就会出现类型转换异常。因为在源码中会将实体类转换为comparable。
具体可以自行查看源码深入探究。
举例:通过Comparable自然排序将Person类中的age进行排序
自然排序使用方法:按照 age
属性的自然排序排序,实现 Comparable
接口并重写 compareTo
方法。
示例代码如下:
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
}
直接调用 Collections.sort
方法就可以对 Person
对象集合进行自然排序,如下所示:
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 30));
Collections.sort(people);
使用Comparator定制排序
举例:通过Comparator定制排序将Person类中的age进行升序排序,对编号进行降序排序
定制排序使用方法:
- 显示定义一个比较器类实现Comparator接口,将比较器对象传入sort方法中
- 直接在Collections.sort中编写comparator的内部类实现
我们采用第二种方法,
假设我们有一个名为Person
的类,其中有三个属性:姓名、年龄、编号。我们想对年龄age进行升序排序,对编号number进行降序排序
@Data
public class Person {
private String name;
private int age;
private int number;
}
现在我们有一个List
对象,其中包含了若干个Person
对象,我们可以使用Collections.sort()
方法对它们进行排序:
List<Person> personList = new ArrayList<>();
personList.add(new Person("Alice", 20, 1000));
personList.add(new Person("Bob", 18,2000));
personList.add(new Person("Charlie", 25,4000));
personList.add(new Person("David", 25,3000));
Collections.sort(personList, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
if (o1.getAge().equals(o2.getAge())){
return o2.getNumber() - o1.getNumber();
}
return o1.getAge() - o2.getAge() ;
}
});
System.out.println(personList);
输出结果为:
Student{name='Bob', age=18, number=2000}
Student{name='Alice', age=20, number=1000}
Student{name='Charlie', age=25, number=4000}
Student{name='David', age=25, number=3000}
Comparator和Comparable的区别
相同点: 在Java中,可以使用Comparator
和Comparable
接口来定义对象的排序方式。
不同点:
- 内部接口和外部接口
- Comparable`接口是Java中的内部排序接口,它需要在类中定义类的自然排序顺序
- Comparator接口是Java中的外部排序接口,它使用外部比较器定制排序顺序,而不修改类本身
- 比较复杂度不同
- 自然排序只能对自己对象间的值进行一个简单比较
- 定制排序可以根据业务需求,实现复杂的排序规则,不仅能比较值,也可以比较长度等,也可以对不同对象的内容进行比较
使用场景
虽然comparator和comparable在功能上没什么区别,都可以实现排序,但是他们在使用场景上还是有区别的
-
使用
Comparable
:- 当对象有一个明显自然的排序方式。
- 希望所有地方默认使用这种排序方式(起到排序复用的效果)。
-
使用
Comparator
:当对象需要多种排序方式,也就是说可以玩花活,当排序逻辑不属于对象的主要功能,而是属于某个特定的排序业务需求,而且对于这个排序,在多个业务中又有着不同的排序需求,其他场景对于该对象又会有其他的排序需求,那就使用comparator吧!