本文主要使用Comparable和Comparator接口来实现对象的排序,以及二维数组的排序功能。
概念
首先了解一下自然排序和定制排序
自然排序:Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo(T t) 方法被称为它的自然比较方法。当前对象this与指定对象t比较“大小”,如果当前对象this大于指定对象t,则返回正整数,如果当前对象this小于指定对象t,则返回负整数,如果当前对象this等于指定对象t,则返回零。
定制排序:强行对某个对象 collection 进行整体排序 的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序。
区别:comparable相当于内部比较器,comparator相当于外部比较器
废话不多说直接上干货
1. Comparable
如果一个类实现了Comparable接口,则意味着该类支持排序,则可以通过Collections.sort(list), Arrays.sort(arr)直接排序,同时对于一些本身就带有排序属性的集合来说,也会按照compareTo方法定义好的排序规则在add元素时进行排序,如PriorityQueue,TreeMap,TreeSet等
1. 先看下他的源码
// 源码非常简单,只有一个方法,实现它即可
public interface Comparable<T> {
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*/
public int compareTo(T o);
}
2.这里创建一个Student类并实现了Comparable
public class Student implements Comparable<Student> {
private Integer id;
private Integer age;
private String address;
public Student(Integer id, Integer age, String address) {
this.id = id;
this.age = age;
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", address='" + address + ''' +
'}';
}
/**
* 按id升序,age降序,address字典序升序(比较的是ASCII值)
*
* @param 指定对象
* @return
*/
@Override
public int compareTo(Student o) {
return this.id.compareTo(o.getId()) == 0 ? o.getAge().compareTo(this.age) == 0 ?
this.address.compareTo(o.address) : o.getAge().compareTo(this.age) : this.id.compareTo(o.getId());
}
3. 然后创建一些测试数据
List<Student> list = new ArrayList<>();
list.add(new Student(3, 3, "B"));
list.add(new Student(3, 2, "A"));
list.add(new Student(10, 13, "b"));
list.add(new Student(10, 13, "c"));
list.add(new Student(10, 13, "a"));
list.add(new Student(6, 23, "w"));
list.add(new Student(6, 12, "w"));
list.add(new Student(6, 12, "a"));
return list;
4. 编写测试类
注意:需要注意的是加入队列后,由于队列是将数组构建成堆的数据结构,所以断点看的时候好像是没有排序的,但是当你使用poll数据时,他其实有序的
public static void main(String[] args) {
List<Student> students = SortUtil.creatData();
SortUtil.myPrint(students);
System.out.println("--------------------集合工具类------------------");
// 使用collections
Collections.sort(students);
students.forEach(stu -> System.out.println(stu));
System.out.println("------------------优先队列-------------------");
// 使用优先队列
PriorityQueue<Student> queue = new PriorityQueue<>();
SortUtil.creatData().forEach(stu -> queue.offer(stu));
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
System.out.println("----------------TreeMap---------------");
// 使用TreeMap,TreeSet源码就是使用TreeMap,不重复测试了
Map<Student, String> map = new TreeMap<>();
SortUtil.creatData().forEach(stu -> map.put(stu, null));
map.forEach((k, v) -> System.out.println(k));
}
5. 查看运行结果,可以看到排序功能正确
--------------------------------
Student{id=3, age=3, address='B'}
Student{id=3, age=2, address='A'}
Student{id=10, age=13, address='b'}
Student{id=10, age=13, address='c'}
Student{id=10, age=13, address='a'}
Student{id=6, age=23, address='w'}
Student{id=6, age=12, address='w'}
Student{id=6, age=12, address='a'}
--------------------集合工具类------------------
Student{id=3, age=3, address='B'}
Student{id=3, age=2, address='A'}
Student{id=6, age=23, address='w'}
Student{id=6, age=12, address='a'}
Student{id=6, age=12, address='w'}
Student{id=10, age=13, address='a'}
Student{id=10, age=13, address='b'}
Student{id=10, age=13, address='c'}
------------------优先队列-------------------
Student{id=3, age=3, address='B'}
Student{id=3, age=2, address='A'}
Student{id=6, age=23, address='w'}
Student{id=6, age=12, address='a'}
Student{id=6, age=12, address='w'}
Student{id=10, age=13, address='a'}
Student{id=10, age=13, address='b'}
Student{id=10, age=13, address='c'}
----------------TreeMap---------------
Student{id=3, age=3, address='B'}
Student{id=3, age=2, address='A'}
Student{id=6, age=23, address='w'}
Student{id=6, age=12, address='a'}
Student{id=6, age=12, address='w'}
Student{id=10, age=13, address='a'}
Student{id=10, age=13, address='b'}
Student{id=10, age=13, address='c'}
2. 实现Comparator
Comparator也叫做定制排序,是比较接口,对于类本身不支持排序,或者实现了Comparable接口,但是排序不符合需求时,则可以使用Comparator实现集合外部排序
Comparator体现了一种策略模式,不改变自身,通过替换不同的comparator比较器就能变换不同的比较规则,更加灵活
1.源码
@FunctionalInterface
public interface Comparator<T> {
/**
* Compares its two arguments for order. Returns a negative integer,
* zero, or a positive integer as the first argument is less than, equal
* to, or greater than the second.<p>
**/
int compare(T o1, T o2);
对于集合工具类,一般都有sort方法,如下所示,只要实现Comparator接口,就能进行排序
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
对于有序集合,则它们就会有将Comparator作为入参的构造方法,如
// 优先队列
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
// treemap
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 如何使用
以Collections类为例,其他同理
// 1.实现自定义类
class MySort implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge().compareTo(o2.getAge());
}
}
Collections.sort(students, new MySort());
// 2.匿名内部类
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge().compareTo(o2.getAge());
}
});
// 3. lambda表达式
Collections.sort(students, (s1, s2) -> s1.getId().compareTo(s2.getId()) == 0 ?
s2.getAge().compareTo(s1.getAge()) == 0 ? s1.getAddress().compareTo(s2.getAddress()) :
s2.getAge().compareTo(s1.getAge()) : s1.getId().compareTo(s2.getId()));
// 4. 方法接口
Collections.sort(students, Comparator.comparing(Student::getId).thenComparing(Student::getAge).reversed()
.thenComparing(Student::getAddress));
利用stream流的sort方法,需要注意的是,流排序之后返回的新数组才是排好序的结果,原数组保持不变
List<Student> collect = SortUtil.creatData().stream().sorted(new MySort()).collect(Collectors.toList());
其他类型基本大同小于,不再过多演示了
3. 二维数组的自定义排序
最后在介绍下更加灵活的排序,二维数组如何对指定列进行排序
每一列按照不同规则排序sortRule = new int[1,0,1];
这里1表示升序,-1表示降序
Arrays.sort(elem, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
int flag;
// 这里的return并不是有一个不相等就返回,而是每个数据都会遍历到,且返回值只是作为比较大小用的,并不是退出方法
for (int i = 0; i < o1.length; i++) {
flag = o1[i] - o2[i];
if (flag != 0) {
return flag * sortRule[i];
}
}
return 0;
}
});
Arrays.sort的底层使用了二分和归并排序,当数组长度小于32,则使用过二分排序,大于等于32,则使用归并阿排序
int[][] arrays = new int[][]{{2, 1, 1}, {1, 2, 2}, {1, 1, 4}, {1, 2, 3}};
// 表示按第一列升序,第二列升序
Arrays.sort(arrays, (arr1, arr2) -> arr1[0] - arr2[0] == 0 ? arr1[1] - arr2[1] : arr1[0] - arr2[0]);