排序是极其常见的使用场景,因为在生活中就有很多这样的实例。国家GDP排名、奥运奖牌排名、明星粉丝排名等,各大排行榜,给人的既是动力,也是压力。
而讲到排序,就会有各种排序算法和相关实现,本文不讲任何排序算法,而只专注于讲使用。通过实例给大家展示,我们可以了解怎样使用既有的工具进行排序。Linux之父说:
Talk is cheap. show me the code!
本文JDK版本为Java 8,但并不代表所介绍到的所有方法只能在JDK1.8上跑,部分方法在之前的版本就已经给出。
如下本次整理的图,记住图中的方法,就能轻松应对大多数场景,赶紧收藏起来吧,哈哈
先上代码:
package java.lang; public interface Comparable<T> { public int compareTo(T o); } 复制代码
可以看出这个接口只有一个方法,这个方法只有一个参数,实现了这个接口的类就可以和同类进行比较了。这个方法所实现的,就是比较法则,也是说,它表示如何对两个对象进行比较。 它返回的是一个整数int:
实现了该接口后,我们就可以使用Arrays.sort()和Collections.sort()来进行排序了。 不然对象没有比较法则,程序肯定是不知道如何进行比较排序的。 像我们常用的类String、Integer、Double、Date等,JDK都帮我们实现了Comparable接口,我们可以直接对这类对象进行比较排序。 举个例子,Date Comparable的实现:
public int compareTo(Date anotherDate) { long thisTime = getMillisOf(this); long anotherTime = getMillisOf(anotherDate); return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1)); } 复制代码
需要注意的是,当我们自己去实现Comparable接口时,一定要注意与**equals()**方法保持一致。当两个对象是equals的,compare的结果应该是相等的。
还是先看代码,看看接口定义吧:
package java.util; @FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); } 复制代码
它是一个函数式接口,它的compare方法有两个参数,代表进行比较的两个对象。这个接口代表了可以作为某种对象比较的一种法则,或叫一种策略。 它的返回值正负代表意义与Comparable接口的方法一样。 它的使用通常会有三种方式:
在Java8之后,我们一般用Lambda比较多,也比较灵活优雅。
两个接口功能都是用于比较排序,但其实有很大的区别。
相对于Comparable,Comparator有一定的灵活性,假如一个类并没有实现Comparable接口,并且这个类是无法修改的,我们就要通过提供Comparator来进行比较排序。 Comparator这种模式实现了数据与算法的解耦合,对于维护也是很方便的。
十分友好的是,JDK为我们提供了工具类,它们的静态方法可以帮助我们直接对数组和List进行排序。
Arrays的sort方法可以对已经实现了Comparable接口的进行排序,同时还可指定排序的范围。
//Arrays.sort对String进行排序 String[] strings = {"de", "dc", "aA", "As", "k", "b"}; Arrays.sort(strings); assertTrue(Arrays.equals(strings, new String[]{"As", "aA", "b", "dc", "de", "k"})); 复制代码
指定范围排序,需要注意的是,index是从0开始算的,包含fromIndex,不包含toIndex:
//Arrays.sort指定范围排序 strings = new String[]{"z", "a", "d", "b"}; Arrays.sort(strings, 0, 3); assertTrue(Arrays.equals(strings, new String[]{"a", "d", "z", "b"})); 复制代码
对于基本类型,一样可以进行排序,并不需要使用封装类:
//Arrays.sort对基本类型排序 int[] nums = {3, 1, 20, 2, 38, 2, 94}; Arrays.sort(nums); assertTrue(Arrays.equals(nums, new int[]{1, 2, 2, 3, 20, 38, 94})); 复制代码
还能多线程进行排序,其实是拆分成多个子数组再进行排序,最终再合并结果。
//Arrays.parallelSort多线程排序 nums = new int[]{3, 1, 20, 2, 38, 2, 94}; Arrays.parallelSort(nums); assertTrue(Arrays.equals(nums, new int[]{1, 2, 2, 3, 20, 38, 94})); 复制代码
对于没有实现Comparable的类也可以使用,但需要提供Comparator来指定比较策略。 本文的没有实现Comparable接口的类Person如下:
import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Person { private String name; private int age; } 复制代码
排序:
//Arrays.sort提供Comparator进行排序 Person[] persons = new Person[]{ new Person("Larry", 18), new Person("David", 30), new Person("James", 20), new Person("Harry", 18)}; Arrays.sort(persons, Comparator.comparingInt(Person::getAge)); assertTrue(Arrays.equals(persons, new Person[]{ new Person("Larry", 18), new Person("Harry", 18), new Person("James", 20), new Person("David", 30)})); 复制代码
JDK的Collections工具类提供了排序方法,可以方便使用。 对于实现Comparable的类进行排序:
//Collections.sort对于实现Comparable的类进行排序 List<String> names = asList("Larry", "Harry", "James", "David"); Collections.sort(names); assertEquals(names, asList("David", "Harry", "James", "Larry")); 复制代码
提供Comparator进行排序:
//Collections.sort提供Comparator进行排序 List<Person> persons2 = asList( new Person("Larry", 18), new Person("David", 30), new Person("James", 20), new Person("Harry", 18)); Collections.sort(persons2, Comparator.comparingInt(Person::getAge)); assertEquals(persons2, asList( new Person("Larry", 18), new Person("Harry", 18), new Person("James", 20), new Person("David", 30))); 复制代码
反序:只是把List反过来,并不代表一定是按照大小顺序的:
//Collections.reverse反序 names = asList("Larry", "Harry", "James", "David"); Collections.reverse(names); assertEquals(names, asList("David", "James", "Harry", "Larry")); 复制代码
List接口有sort(Comparator<? super E> c)方法,可以实现对自身的排序, 会影响自身的顺序 。
//List.sort排序 names = asList("Larry", "Harry", "James", "David"); names.sort(Comparator.naturalOrder()); assertEquals(names, asList("David", "Harry", "James", "Larry")); 复制代码
Stream提供了sorted()和sorted(Comparator<? super T> comparator)进行排序,会返回一个新的Stream。
//Stream.sorted排序 names = asList("Larry", "Harry", "James", "David"); List<String> result = names.stream() .sorted() .collect(Collectors.toList()); assertEquals(result, asList("David", "Harry", "James", "Larry")); //Stream.sorted提供Comparator排序 names = asList("Larry", "Harry", "James", "David"); result = names.stream() .sorted(Comparator.naturalOrder()) .collect(Collectors.toList()); assertEquals(result, asList("David", "Harry", "James", "Larry")); 复制代码
对类的单字段进行排序很简单,只要提供形如:
的Comparator就行了。如果需要倒序,就需要:
具体代码使用(为了不破坏List的原有顺序,我们都使用Stream来操作):
//单字段排序-升序 List<Person> personList = asList( new Person("Larry", 18), new Person("David", 30), new Person("David", 3), new Person("James", 20), new Person("Harry", 18)); List<Person> personResult = personList.stream() .sorted(Comparator.comparing(Person::getName)) .collect(Collectors.toList()); assertEquals(personResult, asList( new Person("David", 30), new Person("David", 3), new Person("Harry", 18), new Person("James", 20), new Person("Larry", 18))); //单字段排序-倒序1 personResult = personList.stream() .sorted(Comparator.comparing(Person::getName).reversed()) .collect(Collectors.toList()); assertEquals(personResult, asList( new Person("Larry", 18), new Person("James", 20), new Person("Harry", 18), new Person("David", 30), new Person("David", 3))); //单字段排序-倒序2 personResult = personList.stream() .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder())) .collect(Collectors.toList()); assertEquals(personResult, asList( new Person("Larry", 18), new Person("James", 20), new Person("Harry", 18), new Person("David", 30), new Person("David", 3))); 复制代码
多字段其实也很方便,只需要用thenComparing进行连接就可以: Comparator.comparing(类名::属性一getter).thenComparing(类名::属性二getter) 具体代码使用例子如下:
//多字段排序-1升2升 personResult = personList.stream() .sorted(Comparator.comparing(Person::getName) .thenComparing(Person::getAge)) .collect(Collectors.toList()); assertEquals(personResult, asList( new Person("David", 3), new Person("David", 30), new Person("Harry", 18), new Person("James", 20), new Person("Larry", 18))); //多字段排序-1升2倒 personResult = personList.stream() .sorted(Comparator.comparing(Person::getName) .thenComparing(Person::getAge, Comparator.reverseOrder())) .collect(Collectors.toList()); assertEquals(personResult, asList( new Person("David", 30), new Person("David", 3), new Person("Harry", 18), new Person("James", 20), new Person("Larry", 18))); //多字段排序-1倒2升 personResult = personList.stream() .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder()) .thenComparing(Person::getAge)) .collect(Collectors.toList()); assertEquals(personResult, asList( new Person("Larry", 18), new Person("James", 20), new Person("Harry", 18), new Person("David", 3), new Person("David", 30))); //多字段排序-1倒2倒 personResult = personList.stream() .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder()) .thenComparing(Person::getAge, Comparator.reverseOrder())) .collect(Collectors.toList()); assertEquals(personResult, asList( new Person("Larry", 18), new Person("James", 20), new Person("Harry", 18), new Person("David", 30), new Person("David", 3))); 复制代码
本文从比较排序相关的两个接口(Comparable和Comparator)讲起,并以代码实例的形式,讲解了Array、List、Stream排序的方法,这应该可以覆盖大部分Java排序的使用场景。 对于其它集合类如Set和Map,一样可以进行排序处理,可以将它们转化为Stream然后再进行排序。