當(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ī)則
但是有一天Kobe和Messi起了爭執(zhí),Kobe是Messi的上司,Kobe對Messi說:“我比你大,你得聽我的...”,上面的語境很明顯能判斷出Kobe說的是職級而并不是年齡,這時候用代碼怎么表示呢?
- 改代碼咯,原來是比較年齡的,現(xiàn)在改成比較職級。這樣是不合適的,假如等會又要按年齡比較,是不是還要改代碼?或者說
Person類是別人jar包中的類,我沒有源碼怎么改。 - 繼承
Person類,覆蓋compareTo()方法,這種方法可行,但是不夠靈活,不好擴展,父子類之間耦合性太強。比如說兩種比較規(guī)則要組合在一起使用時。 - 既然繼承沒有優(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)話,Kobe、Messi、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、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
按照對象內(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。