Comparable和Comparator

當(dāng)你想要比較兩個(gè)對(duì)象時(shí),首先想到的是定義一個(gè)比較器段誊,比較器中規(guī)定了這兩個(gè)對(duì)象的比較規(guī)則,當(dāng)你需要對(duì)某個(gè)集合進(jìn)行排序時(shí)栈拖,只需要將這個(gè)比較器傳給排序程序里就行了连舍。可以這么說涩哟,比較器就是對(duì)比較規(guī)則的封裝索赏。

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

但是在日常比較中器仗,除了使用比較器比較外融涣,某些對(duì)象應(yīng)該擁有默認(rèn)的比較規(guī)則,這些規(guī)則是大家熟知的精钮。就像我對(duì)你說我比你大威鹿,大家都知道說的是年齡,就像我們常說的這只狗比較大轨香,比較的也是尺寸忽你。這種默認(rèn)的比較規(guī)則是不需要比較器去定義的,只要同一類的對(duì)象都知道就行了臂容。API把這種比較稱作自然比較科雳。實(shí)現(xiàn)Comparable的類,為此類的所有對(duì)象提供了自然或者默認(rèn)的比較規(guī)則實(shí)現(xiàn)策橘。對(duì)這些類進(jìn)行排序或者比較時(shí)炸渡,就不需要比較器了。

Comparable-默認(rèn)規(guī)則實(shí)現(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實(shí)現(xiàn)了Comparable接口丽已,規(guī)定每個(gè)Person對(duì)象在比較時(shí)默認(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ēng)執(zhí),KobeMessi的上司沛婴,Kobe對(duì)Messi說:“我比你大吼畏,你得聽我的...”,上面的語境很明顯能判斷出Kobe說的是職級(jí)而并不是年齡嘁灯,這時(shí)候用代碼怎么表示呢?

  1. 改代碼咯泻蚊,原來是比較年齡的,現(xiàn)在改成比較職級(jí)丑婿。這樣是不合適的性雄,假如等會(huì)又要按年齡比較没卸,是不是還要改代碼?或者說Person類是別人jar包中的類秒旋,我沒有源碼怎么改约计。
  2. 繼承Person類,覆蓋compareTo()方法迁筛,這種方法可行煤蚌,但是不夠靈活,不好擴(kuò)展细卧,父子類之間耦合性太強(qiáng)尉桩。比如說兩種比較規(guī)則要組合在一起使用時(shí)。
  3. 既然繼承沒有優(yōu)勢(shì)贪庙,那用組合總行吧蜘犁,組合擁有更強(qiáng)的靈活性和可擴(kuò)展性。所以比較器出現(xiàn)了插勤,上面的Person實(shí)現(xiàn)了Comparable接口沽瘦,只是規(guī)定了默認(rèn)的比較規(guī)則,有沒規(guī)定比較時(shí)必須使用這個(gè)規(guī)則农尖。我用比較器定義一個(gè)我自己的規(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ī)則進(jìn)行比較了助隧,只需要第一號(hào)相應(yīng)的規(guī)則就好。比較器在排序中也非常重要滑沧,某一天公司老板把所有的Person召集到一起訓(xùn)話并村,KobeMessi滓技、Tom等到場(chǎng)后哩牍,老板說,你們按順序坐在第一排令漂,說完就去忙別的了膝昆,所有Person就按照默認(rèn)的規(guī)則順序的坐在前排,鄧?yán)习寤貋淼兀B忙說荚孵,錯(cuò)了錯(cuò)了,我是說按職級(jí)的順序坐纬朝。這就是在現(xiàn)實(shí)生活中按xxx排序的場(chǎng)景收叶。在這樣的場(chǎng)景中,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)的匿名類判没,我就渾身不舒服嗡髓,每一個(gè)比較規(guī)則都要去構(gòu)造一個(gè)比較器豈不是很麻煩么扶踊。放心帽芽,Comparator為我們提供了許多實(shí)用的構(gòu)造方式摄咆。

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

對(duì)象內(nèi)部某個(gè)key實(shí)現(xiàn)了Comparable接口,并且這個(gè)key的默認(rèn)比較規(guī)則符合現(xiàn)實(shí)場(chǎng)景摊阀。

//按照name排序,name字段的類型必須實(shí)現(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

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

如果對(duì)象內(nèi)部某個(gè)key是基本數(shù)據(jù)類型如int踪蹬、long胞此、double等。

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

Toma
TOmb
Messi
Kobe

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

如果對(duì)象內(nèi)部某個(gè)key沒有實(shí)現(xiàn)Comparable接口跃捣,或者這個(gè)key的默認(rèn)比較規(guī)則仍不符合場(chǎng)景的情況漱牵。這種構(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
//按名字長(zhǎng)度排序
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)造

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

//自然(默認(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)造

解決這種需求,主字段排序和輔字段排序問題丸升,比如兩個(gè)Person對(duì)象比較铆农,如果年齡相同則比較他們的職級(jí)。

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é)

在談到比較和排序時(shí)狡耻,首先想到的應(yīng)該是Comparator而不是Comparable墩剖,Comparable是將對(duì)象和對(duì)象的比較行為耦合在一起,Comparator是將二者解耦夷狰。說到底一個(gè)是NatureOrder 一個(gè) 是 CustomOrder岭皂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沼头,隨后出現(xiàn)的幾起案子爷绘,更是在濱河造成了極大的恐慌,老刑警劉巖瘫证,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揉阎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡背捌,警方通過查閱死者的電腦和手機(jī)毙籽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毡庆,“玉大人坑赡,你說我怎么就攤上這事烙如。” “怎么了毅否?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵亚铁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我螟加,道長(zhǎng)徘溢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任捆探,我火速辦了婚禮然爆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黍图。我一直安慰自己曾雕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布助被。 她就那樣靜靜地躺著剖张,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揩环。 梳的紋絲不亂的頭發(fā)上搔弄,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音检盼,去河邊找鬼肯污。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吨枉,可吹牛的內(nèi)容都是我干的蹦渣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼貌亭,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柬唯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起圃庭,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤锄奢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后剧腻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拘央,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年书在,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灰伟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儒旬,死狀恐怖栏账,靈堂內(nèi)的尸體忽然破棺而出帖族,到底是詐尸還是另有隱情,我是刑警寧澤挡爵,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布竖般,位于F島的核電站,受9級(jí)特大地震影響茶鹃,放射性物質(zhì)發(fā)生泄漏涣雕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一闭翩、第九天 我趴在偏房一處隱蔽的房頂上張望胞谭。 院中可真熱鬧,春花似錦男杈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至彩库,卻和暖如春肤无,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骇钦。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工宛渐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人眯搭。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓窥翩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鳞仙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寇蚊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

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