【Java必修課】一圖說盡排序,一文細說Sorting(Array、List、Stream的排序)

簡說排序

排序是極其常見的使用場景,由於在生活中就有不少這樣的實例。國家GDP排名、奧運獎牌排名、明星粉絲排名等,各大排行榜,給人的既是動力,也是壓力。java

而講到排序,就會有各類排序算法和相關實現,本文不講任何排序算法,而只專一於講使用。經過實例給你們展現,咱們能夠了解怎樣使用既有的工具進行排序。Linux之父說:算法

Talk is cheap. show me the code!數組

本文JDK版本爲Java 8,但並不表明所介紹到的全部方法只能在JDK1.8上跑,部分方法在以前的版本就已經給出。多線程

以下本次整理的圖,記住圖中的方法,就能輕鬆應對大多數場景,趕忙收藏起來吧,哈哈 Java排序函數


兩個接口

Comparable

先上代碼:工具

package java.lang;

public interface Comparable<T> {  
    public int compareTo(T o);
}

能夠看出這個接口只有一個方法,這個方法只有一個參數,實現了這個接口的類就能夠和同類進行比較了。這個方法所實現的,就是比較法則,也是說,它表示如何對兩個對象進行比較。 它返回的是一個整數int:this

  • 返回正數,表明當前對象大於被比較的對象;
  • 返回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的結果應該是相等的。3d

Comparator

仍是先看代碼,看看接口定義吧:code

package java.util;

@FunctionalInterface
public interface Comparator<T> {   
    int compare(T o1, T o2);    
}

它是一個函數式接口,它的compare方法有兩個參數,表明進行比較的兩個對象。這個接口表明了能夠做爲某種對象比較的一種法則,或叫一種策略。 它的返回值正負表明意義與Comparable接口的方法同樣。 它的使用一般會有三種方式:

  1. 實現類
  2. 匿名類
  3. Lambda表達式

在Java8以後,咱們通常用Lambda比較多,也比較靈活優雅。

兩個接口的比較

兩個接口功能都是用於比較排序,但其實有很大的區別。

  1. 二者方法參數不一樣,Comparable只有一個參數,表示被比較的對象,由於它的方法是位於須要比較的類裏的,因此只要一個參數就能夠了;而Comparator的比較方法則有兩個參數,分別表示比較對象和被比較對象。
  2. Comparable與對象綁定,位於對象內,咱們能夠稱之爲內比較器;而Comparator是獨立於須要比較的類的,咱們能夠稱爲外比較器
  3. 當類實現了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", "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)}));

List排序Collections

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排序

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排序

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

單字段排序

對類的單字段進行排序很簡單,只要提供形如:

  • Comparator.comparing(類名::屬性getter)

的Comparator就好了。若是須要倒序,就須要:

  • Comparator.comparing(類名::屬性getter).reversed()
  • Comparator.comparing(類名::屬性getter, Comparator.reverseOrder())

具體代碼使用(爲了避免破壞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而後再進行排序。

相關文章
相關標籤/搜索