Comparable和Comparator

當(dāng)你想要比較兩個對象時,首先想到的是定義一個比較器,比較器中規(guī)定了這兩個對象的比較規(guī)則,當(dāng)你需要對某個集合進行排序時,只需要將這個比較器傳給排序程序里就行了??梢赃@么說,比較器就是對比較規(guī)則的封裝。

Comparator就是一個比較器的封裝類,所以他放在java.util包中,int compare(T o1, T o2);方法是比較器的核心方法,該方法實現(xiàn)具體的比較規(guī)則。

但是在日常比較中,除了使用比較器比較外,某些對象應(yīng)該擁有默認(rèn)的比較規(guī)則,這些規(guī)則是大家熟知的。就像我對你說我比你大,大家都知道說的是年齡,就像我們常說的這只狗比較大,比較的也是尺寸。這種默認(rèn)的比較規(guī)則是不需要比較器去定義的,只要同一類的對象都知道就行了。API把這種比較稱作自然比較。實現(xiàn)Comparable的類,為此類的所有對象提供了自然或者默認(rèn)的比較規(guī)則實現(xiàn)。對這些類進行排序或者比較時,就不需要比較器了。

Comparable-默認(rèn)規(guī)則實現(xiàn)

public class Person implements Comparable<Person>{

    private String name;
    private int age;
    //指的是職位
    private int position;

    public Person(String name,int age,int position) {
        this.name = name;
        this.age = age;
        this.position = position;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }

    @Override
    public int compareTo(Person o) {
        return this.age > o.getAge() ? 1 : (this.age == o.getAge() ? 0 : -1);
    }
}

上面的Person實現(xiàn)了Comparable接口,規(guī)定每個Person對象在比較時默認(rèn)比較的是年齡,比如:

public static void main(String[] args) {
    Person person1 = new Person("Messi",35,2);
    Person person2 = new Person("Kobe",31,4);
    if(person1.compareTo(person2) == 1) {
        System.out.println(person1.getName() + "要比" + person2.getName() + "大...");
    } else if(person1.compareTo(person2) == -1){
        System.out.println(person2.getName() + "要比" + person1.getName() + "大...");
    } else {
        System.out.println(person2.getName() + "和" + person1.getName() + "一樣大...");
    }
}

Messi要比Kobe大...

Comparator-定制比較規(guī)則

但是有一天KobeMessi起了爭執(zhí),KobeMessi的上司,KobeMessi說:“我比你大,你得聽我的...”,上面的語境很明顯能判斷出Kobe說的是職級而并不是年齡,這時候用代碼怎么表示呢?

  1. 改代碼咯,原來是比較年齡的,現(xiàn)在改成比較職級。這樣是不合適的,假如等會又要按年齡比較,是不是還要改代碼?或者說Person類是別人jar包中的類,我沒有源碼怎么改。
  2. 繼承Person類,覆蓋compareTo()方法,這種方法可行,但是不夠靈活,不好擴展,父子類之間耦合性太強。比如說兩種比較規(guī)則要組合在一起使用時。
  3. 既然繼承沒有優(yōu)勢,那用組合總行吧,組合擁有更強的靈活性和可擴展性。所以比較器出現(xiàn)了,上面的Person實現(xiàn)了Comparable接口,只是規(guī)定了默認(rèn)的比較規(guī)則,有沒規(guī)定比較時必須使用這個規(guī)則。我用比較器定義一個我自己的規(guī)則就行了。
public static void main(String[] args) {
    Person person1 = new Person("Messi",35,2);
    Person person2 = new Person("Kobe",31,4);
    Comparator<Person> comparator = new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.getPosition() > o2.getPosition() ? 1 : (o1.getPosition() == o2.getPosition() ? 0 : -1);
        }
    };
    if(comparator.compare(person1,person2) == 1) {
        System.out.println(person1.getName() + "要比" + person2.getName() + "大...");
    } else if(comparator.compare(person1,person2) == -1){
        System.out.println(person2.getName() + "要比" + person1.getName() + "大...");
    } else {
        System.out.println(person2.getName() + "和" + person1.getName() + "一樣大...");
    }
}

Kobe要比Messi大...

可以看到,有了比較器,我可以按照任何規(guī)則進行比較了,只需要第一號相應(yīng)的規(guī)則就好。比較器在排序中也非常重要,某一天公司老板把所有的Person召集到一起訓(xùn)話,KobeMessi、Tom等到場后,老板說,你們按順序坐在第一排,說完就去忙別的了,所有Person就按照默認(rèn)的規(guī)則順序的坐在前排,鄧?yán)习寤貋恚B忙說,錯了錯了,我是說按職級的順序坐。這就是在現(xiàn)實生活中按xxx排序的場景。在這樣的場景中,Comparator起到了無可替代的作用。


Person person1 = new Person("Messi",35,2);
Person person2 = new Person("Kobe",31,4);
Person person3 = new Person("Tom",25,1);

List<Person> personList = new ArrayList<>(Arrays.asList(new Person[]{person1,person2,person3}));
//默認(rèn)排序
Collections.sort(personList);
personList.stream().map(Person::getName).forEach(Systm.out::println);
/*
    Tom
    Kobe
    Messi
*/
//定制規(guī)則排序
Comparator<Person> comparator = new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getPosition() > o2.getPosition() ? 1 : (o1.getPosition() == o2.getPosition() ? 0 : -1);
    }
};
Collections.sort(personList,comparator);
//personList.sort(comparator);

personList.stream().map(Person::getName).forEach(Systm.out::println);
/*
    Tom
    Messi
    Kobe
*/

構(gòu)造Comparator

看到上面出現(xiàn)的匿名類,我就渾身不舒服,每一個比較規(guī)則都要去構(gòu)造一個比較器豈不是很麻煩么。放心,Comparator為我們提供了許多實用的構(gòu)造方式。

按照對象內(nèi)部一個可排序的key構(gòu)造

對象內(nèi)部某個key實現(xiàn)了Comparable接口,并且這個key的默認(rèn)比較規(guī)則符合現(xiàn)實場景。

//按照name排序,name字段的類型必須實現(xiàn)Comparable接口
Comparator<Person> byName = Comparator.comparing(Person::getName);
personList.sort(byName);
personList.stream().map(Person::getName).forEach(System.out::println);

Kobe
Messi
TOmb
Toma

對象內(nèi)部基本數(shù)據(jù)類型的比較

如果對象內(nèi)部某個key是基本數(shù)據(jù)類型如int、longdouble等。

Comparator<Person> byPosition = Comparator.comparingInt(Person::getPosition);
personList.sort(byPosition);
personList.stream().map(Person::getName).forEach(System.out::println);

Toma
TOmb
Messi
Kobe

按照對象內(nèi)部一個key和給定的key比較器構(gòu)造

如果對象內(nèi)部某個key沒有實現(xiàn)Comparable接口,或者這個key的默認(rèn)比較規(guī)則仍不符合場景的情況。這種構(gòu)造方式可以將key的比較策略一直挖掘下去,直到出現(xiàn)基本數(shù)據(jù)。

//按照名稱排序,忽略大小寫
Comparator<Person> byNameIgnoreCase = Comparator.comparing(Person::getName,Comparator.comparing(String::toLowerCase));
//效果同上
//Comparator<Person> byNameIgnoreCase = Comparator.comparing(Person::getName,String.CASE_INSENSITIVE_ORDER);
personList.sort(byNameIgnoreCase);
personList.stream().map(Person::getName).forEach(System.out::println);

Kobe
Messi
Toma
TOmb
//按名字長度排序
Comparator<Person> byNameLength = Comparator.comparing(Person::getName,Comparator.comparingInt(String::length));
personList.sort(byNameLength);
personList.stream().map(Person::getName).forEach(System.out::println);

Kobe
Toma
TOmb
Messi

按照自然排序規(guī)則構(gòu)造

就是按照對象本身默認(rèn)的規(guī)則構(gòu)造一個比較器。

//自然(默認(rèn))排序規(guī)則
Comparator<Person> byNature = Comparator.naturalOrder();
personList.sort(byNature);
personList.stream().map(Person::getName).forEach(System.out::println);

Toma
TOmb
Kobe
Messi

集合中存在null的情況

將null放在前面

Comparator<Person> nullFirst = Comparator.nullsFirst(Comparator.naturalOrder());
personList.sort(nullFirst);
personList.stream().map(o -> o == null?"null":o.getName()).forEach(System.out::println);

null
Toma
TOmb
Kobe
Messi

將null放在后面

Comparator<Person> nullLast = Comparator.nullsLast(Comparator.naturalOrder());
personList.sort(nullLast);
personList.stream().map(o -> o == null?"null":o.getName()).forEach(System.out::println);

Toma
TOmb
Kobe
Messi
null

倒序排序

將已知的比較器反轉(zhuǎn)。

//反轉(zhuǎn)
Comparator<Person> byPositionReverse = Comparator.comparingInt(Person::getPosition).reversed();
personList.sort(byPositionReverse);
personList.stream().map(Person::getName).forEach(System.out::println);

Kobe
Messi
Toma
TOmb

自然排序的倒序。

Comparator<Person> byNatureReverse = Comparator.reverseOrder();
personList.sort(byNatureReverse);
personList.stream().map(Person::getName).forEach(System.out::println);

Messi
Kobe
Toma
TOmb

復(fù)合構(gòu)造

解決這種需求,主字段排序和輔字段排序問題,比如兩個Person對象比較,如果年齡相同則比較他們的職級。

Comparator<Person> complexComparator =Comparator.comparingInt(Person::getAge).thenComparing(Comparator.comparingInt(Person::getPosition));
//Comparator<Person> complexComparator = Comparator.comparingInt(Person::getAge).thenComparing(Person::getName,String.CASE_INSENSITIVE_ORDER);
personList.sort(complexComparator);
personList.stream().map(Person::getName).forEach(System.out::println);

總結(jié)

在談到比較和排序時,首先想到的應(yīng)該是Comparator而不是Comparable,Comparable是將對象和對象的比較行為耦合在一起,Comparator是將二者解耦。說到底一個是NatureOrder 一個 是 CustomOrder。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Comparable定義: Comparable是一個排序接口,當(dāng)一個類實現(xiàn)了該接口,就意味著“該類支持排序”。 ...
    大海孤了島閱讀 927評論 1 3
  • 由于時間是不會重復(fù)的,所以這個隨機數(shù)永不重復(fù),但是受限制的是每一秒鐘只會生成1000個隨機數(shù)。測試 結(jié)果:本工具生...
    吳業(yè)鵬閱讀 957評論 0 0
  • 高二相識,今年已經(jīng)大學(xué)畢業(yè)了。 第一次表白你說我對你不是那樣的感情啊,我說那是什么 ——最佳損友。一如第二次我替你...
    張關(guān)保閱讀 320評論 0 0
  • 兒子狀態(tài)好了很多。我發(fā)現(xiàn)已經(jīng)不用我擔(dān)心了。我可以做自己喜歡的事情了。以前都是我做自己的事或?qū)W習(xí)或練字、只是默...
    梅燕霓閱讀 93評論 0 1
  • 什么是ListView? ListView是在Android開發(fā)中使用得相當(dāng)頻繁的一個控件,它的作用是用來展示一組...
    lhccccc閱讀 1,031評論 0 0

友情鏈接更多精彩內(nèi)容