當(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ī)則
但是有一天Kobe
和Messi
起了爭(zhēng)執(zhí),Kobe
是Messi
的上司沛婴,Kobe
對(duì)Messi
說:“我比你大吼畏,你得聽我的...”,上面的語境很明顯能判斷出Kobe
說的是職級(jí)而并不是年齡嘁灯,這時(shí)候用代碼怎么表示呢?
- 改代碼咯泻蚊,原來是比較年齡的,現(xiàn)在改成比較職級(jí)丑婿。這樣是不合適的性雄,假如等會(huì)又要按年齡比較没卸,是不是還要改代碼?或者說
Person
類是別人jar
包中的類秒旋,我沒有源碼怎么改约计。 - 繼承
Person
類,覆蓋compareTo()
方法迁筛,這種方法可行煤蚌,但是不夠靈活,不好擴(kuò)展细卧,父子類之間耦合性太強(qiáng)尉桩。比如說兩種比較規(guī)則要組合在一起使用時(shí)。 - 既然繼承沒有優(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)話并村,Kobe
、Messi
滓技、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
岭皂。