【Java必修課】一圖說盡排序,一文細說Sorting(Array、List、Stream的排序)
簡說排序
排序是極其常見的使用場景,因為在生活中就有很多這樣的例項。國家GDP排名、奧運獎牌排名、明星粉絲排名等,各大排行榜,給人的既是動力,也是壓力。
而講到排序,就會有各種排序演演算法和相關實現,本文不講任何排序演演算法,而只專注於講使用。通過例項給大家展示,我們可以瞭解怎樣使用既有的工具進行排序。Linux之父說:
Talk is cheap. show me the code!
本文JDK版本為Java 8,但並不代表所介紹到的所有方法只能在JDK1.8上跑,部分方法在之前的版本就已經給出。
如下本次整理的圖,記住圖中的方法,就能輕鬆應對大多數場景,趕緊收藏起來吧,哈哈
兩個介面
Comparable
先上程式碼:
package java.lang;
public interface Comparable<T> {
public int compareTo(T o);
}
複製程式碼
可以看出這個介面只有一個方法,這個方法只有一個引數,實現了這個介面的類就可以和同類進行比較了。這個方法所實現的,就是比較法則,也是說,它表示如何對兩個物件進行比較。 它返回的是一個整數int:
- 返回正數,代表當前物件大於被比較的物件;
- 返回0,代表當前物件等於於被比較的物件;
- 返回負數,代表當前物件小於被比較的物件。
實現了該介面後,我們就可以使用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的結果應該是相等的。
Comparator
還是先看程式碼,看看介面定義吧:
package java.util;
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1,T o2);
}
複製程式碼
它是一個函式式介面,它的compare方法有兩個引數,代表進行比較的兩個物件。這個介面代表了可以作為某種物件比較的一種法則,或叫一種策略。 它的返回值正負代表意義與Comparable介面的方法一樣。 它的使用通常會有三種方式:
- 實現類
- 匿名類
- Lambda表示式
在Java8之後,我們一般用Lambda比較多,也比較靈活優雅。
兩個介面的比較
兩個介面功能都是用於比較排序,但其實有很大的區別。
- 兩者方法引數不同,Comparable只有一個引數,表示被比較的物件,因為它的方法是位於需要比較的類裡的,所以只要一個引數就可以了;而Comparator的比較方法則有兩個引數,分別表示比較物件和被比較物件。
- Comparable與物件繫結,位於物件內,我們可以稱之為內比較器;而Comparator是獨立於需要比較的類的,我們可以稱為外比較器。
- 當類實現了Comparable方法後,比較法則就確定了,我們稱之為自然比較方法,我們無法給它實現多種比較方法;而因為Comparator獨立於外,我們可以為同一個類提供多種Comparator的實現,這樣來提供多種比較方法/策略,如升序倒序,因此我們也可以將Comparator看成是一種策略模式。
相對於Comparable,Comparator有一定的靈活性,假如一個類並沒有實現Comparable介面,並且這個類是無法修改的,我們就要通過提供Comparator來進行比較排序。 Comparator這種模式實現了資料與演演算法的解耦合,對於維護也是很方便的。
工具類
十分友好的是,JDK為我們提供了工具類,它們的靜態方法可以幫助我們直接對陣列和List進行排序。
陣列排序Arrays
Arrays的sort方法可以對已經實現了Comparable介面的進行排序,同時還可指定排序的範圍。
//Arrays.sort對String進行排序
String[] strings = {"de","dc","aA","As","k","b"};
Arrays.sort(strings);
assertTrue(Arrays.equals(strings,new String[]{"As","b","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","z","b"}));
複製程式碼
對於基本型別,一樣可以進行排序,並不需要使用封裝類:
//Arrays.sort對基本型別排序
int[] nums = {3,1,20,2,38,94};
Arrays.sort(nums);
assertTrue(Arrays.equals(nums,new int[]{1,3,94}));
複製程式碼
還能多執行緒進行排序,其實是拆分成多個子陣列再進行排序,最終再合併結果。
//Arrays.parallelSort多執行緒排序
nums = new int[]{3,94};
Arrays.parallelSort(nums);
assertTrue(Arrays.equals(nums,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",30)}));
複製程式碼
List排序Collections
JDK的Collections工具類提供了排序方法,可以方便使用。 對於實現Comparable的類進行排序:
//Collections.sort對於實現Comparable的類進行排序
List<String> names = asList("Larry","Harry","James","David");
Collections.sort(names);
assertEquals(names,asList("David","Larry"));
複製程式碼
提供Comparator進行排序:
//Collections.sort提供Comparator進行排序
List<Person> persons2 = asList(
new Person("Larry",18));
Collections.sort(persons2,Comparator.comparingInt(Person::getAge));
assertEquals(persons2,asList(
new Person("Larry",30)));
複製程式碼
反序:只是把List反過來,並不代表一定是按照大小順序的:
//Collections.reverse反序
names = asList("Larry","David");
Collections.reverse(names);
assertEquals(names,"Larry"));
複製程式碼
成員方法
List排序
List介面有sort(Comparator<? super E> c)方法,可以實現對自身的排序,會影響自身的順序。
//List.sort排序
names = asList("Larry","David");
names.sort(Comparator.naturalOrder());
assertEquals(names,"Larry"));
複製程式碼
Stream排序
Stream提供了sorted()和sorted(Comparator<? super T> comparator)進行排序,會返回一個新的Stream。
//Stream.sorted排序
names = asList("Larry","David");
List<String> result = names.stream()
.sorted()
.collect(Collectors.toList());
assertEquals(result,"Larry"));
//Stream.sorted提供Comparator排序
names = asList("Larry","David");
result = names.stream()
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
assertEquals(result,"Larry"));
複製程式碼
方便物件排序的Comparator
單欄位排序
對類的單欄位進行排序很簡單,只要提供形如:
- Comparator.comparing(類名::屬性getter)
的Comparator就行了。如果需要倒序,就需要:
- Comparator.comparing(類名::屬性getter).reversed()
- 或Comparator.comparing(類名::屬性getter,Comparator.reverseOrder())。
具體程式碼使用(為了不破壞List的原有順序,我們都使用Stream來操作):
//單欄位排序-升序
List<Person> personList = asList(
new Person("Larry",3),18));
List<Person> personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName))
.collect(Collectors.toList());
assertEquals(personResult,asList(
new Person("David",new Person("Larry",18)));
//單欄位排序-倒序1
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName).reversed())
.collect(Collectors.toList());
assertEquals(personResult,3)));
//單欄位排序-倒序2
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName,Comparator.reverseOrder()))
.collect(Collectors.toList());
assertEquals(personResult,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,18)));
//多欄位排序-1升2倒
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge,18)));
//多欄位排序-1倒2升
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName,Comparator.reverseOrder())
.thenComparing(Person::getAge))
.collect(Collectors.toList());
assertEquals(personResult,30)));
//多欄位排序-1倒2倒
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName,Comparator.reverseOrder())
.thenComparing(Person::getAge,3)));
複製程式碼
總結
本文從比較排序相關的兩個介面(Comparable和Comparator)講起,並以程式碼例項的形式,講解了Array、List、Stream排序的方法,這應該可以覆蓋大部分Java排序的使用場景。 對於其它集合類如Set和Map,一樣可以進行排序處理,可以將它們轉化為Stream然後再進行排序。