什么是Comparable接口?
Comparable接口一般用于表示某個(gè)實(shí)例具有內(nèi)在的排序關(guān)系。
為什么需要實(shí)現(xiàn)Comparable接口?
我們知道,對(duì)于普通的數(shù)值或者字符串,都可以進(jìn)行一定的排序。但是,能不能直接給對(duì)象進(jìn)行排序呢?答案當(dāng)然是不能的了,實(shí)際上,之所以我們可以對(duì)數(shù)值和字符串進(jìn)行排序,是因?yàn)橄到y(tǒng)內(nèi)部已經(jīng)為我們定義了數(shù)值和字符串的排序關(guān)系。而我們定義的對(duì)象,本身是不包含排序關(guān)系的,因此,我們無(wú)法直接對(duì)對(duì)象進(jìn)行排序。
因此,如果我們需要對(duì)對(duì)象進(jìn)行排序的話,就必須定義對(duì)象的內(nèi)在排序關(guān)系,即實(shí)現(xiàn)Comparable接口。
如何實(shí)現(xiàn)Comparable接口?
我們舉個(gè)具體的例子,我們定義一個(gè)person對(duì)象,要求person按照年齡來(lái)進(jìn)行排序。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
對(duì)于,上面的對(duì)象,我們是不能直接進(jìn)行排序的,必須要實(shí)現(xiàn)其Comparable接口:
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person o) {
if (this.age > o.age)
return 1;
else if (this.age < o.age)
return -1;
else return 0;
}
@Override
public String toString() {
return "name: " + name + " age: " + age;
}
public static void main(String[] args){
List<Person> list = new ArrayList<>();
list.add(new Person("John",18));
list.add(new Person("Marry",21));
list.add(new Person("Tom",20));
System.out.println("Before sort:");
printList(list);
Collections.sort(list);
System.out.println("After sort:");
printList(list);
}
public static void printList(List<Person> list){
for (Person p : list){
System.out.print(p + " / ");
}
System.out.println();
}
}
輸出結(jié)果:
Before sort:
name: John age: 18 / name: Marry age: 21 / name: Tom age: 20 /
After sort:
name: John age: 18 / name: Tom age: 20 / name: Marry age: 21 /
為了方便測(cè)試,我們重寫了toString方法,然后調(diào)用Collections.sort()方法來(lái)對(duì)list進(jìn)行排序。
現(xiàn)在我們重點(diǎn)來(lái)看實(shí)現(xiàn)Comparable接口中的CompareTo方法:
@Override
public int compareTo(Person o) {
if (this.age > o.age)
return 1;
else if (this.age < o.age)
return -1;
else return 0;
}
CompareTo方法返回三個(gè)結(jié)果,1,0,-1,那么它們分別代表什么意思呢?
- 首先我們要知道我們比較的是this對(duì)象( 當(dāng)前對(duì)象 )和被比較的對(duì)象。
- 其中,0表示兩者相等
- -1表示當(dāng)前對(duì)象排在被比較對(duì)象之前
- 1表示當(dāng)前對(duì)象排在被比較對(duì)象之后
我們上面實(shí)現(xiàn)的是對(duì)person中的age升序排列,因?yàn)楫?dāng)this.age<o.age
時(shí),返回的是-1,表示this對(duì)象排在對(duì)象o前面。也就是較小的數(shù)排在前面。那么如果我們要改為降序呢?
@Override
public int compareTo(Person o) {
if (this.age < o.age)
return 1;
else if (this.age > o.age)
return -1;
else return 0;
}
實(shí)際上,只要修改下比較號(hào)即可。
多重比較
對(duì)于,上面的Person類,可能出現(xiàn)age相同的情況,如果我還想繼續(xù)排序,假設(shè)age相同時(shí),對(duì)name進(jìn)行排序。那么這種情況又應(yīng)該如何實(shí)現(xiàn)呢?
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person o) {
if (age > o.age)
return 1;
if (age < o.age)
return -1;
return name.compareTo(o.name);
}
@Override
public String toString() {
return "name: " + name + " age: " + age;
}
public static void main(String[] args){
List<Person> list = new ArrayList<>();
list.add(new Person("John",18));
list.add(new Person("Marry",21));
list.add(new Person("Tom",20));
list.add(new Person("Mark",20));
list.add(new Person("Ruby",20));
System.out.println("Before sort:");
printList(list);
Collections.sort(list);
System.out.println("After sort:");
printList(list);
}
public static void printList(List<Person> list){
for (Person p : list){
System.out.print(p + " / ");
}
System.out.println();
}
}
輸出結(jié)果:
Before sort:
name: John age: 18 / name: Marry age: 21 / name: Tom age: 20 / name: Mark age: 20 / name: Ruby age: 20 /
After sort:
name: John age: 18 / name: Mark age: 20 / name: Ruby age: 20 / name: Tom age: 20 / name: Marry age: 21 /
我們可以看到,當(dāng)age相同時(shí),Mark,Ruby和Tom按照名字中的字母進(jìn)行排序。達(dá)到了我們需要的效果,我們具體來(lái)看下是如何實(shí)現(xiàn)的:
@Override
public int compareTo(Person o) {
if (age > o.age)
return 1;
if (age < o.age)
return -1;
//若年齡相等,則直接通過(guò)名字來(lái)進(jìn)行排序。
return name.compareTo(o.name);
}
實(shí)際上,也就是當(dāng)年齡相等時(shí),調(diào)用name的compare方法來(lái)進(jìn)行排序。為什么這里可以直接調(diào)用compare方法呢?因?yàn)樽址谙到y(tǒng)中已經(jīng)設(shè)置好了內(nèi)部排序。這里默認(rèn)為升序,那如果需要設(shè)置降序呢?
return -name.compareTo(o.name);
修改為其相反數(shù)即可。
再度升級(jí),如果為Person類添加一個(gè)id屬性,并要求先按id升序,若id相同,則按年齡降序,若id和age都相同,則按name升序。
public class Person implements Comparable<Person>{
private int id;
private String name;
private int age;
public Person(int id,String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person o) {
if (id > o.id)
return 1;
if (id < o.id)
return -1;
//id相等的情況下,對(duì)age進(jìn)行排序
if (age > o.age)
return -1;
if (age < o.age)
return 1;
//若id和age相等,則直接通過(guò)名字來(lái)進(jìn)行排序。
return name.compareTo(o.name);
}
@Override
public String toString() {
return "id: " + id + " name: " + name + " age: " + age;
}
public static void main(String[] args){
List<Person> list = new ArrayList<>();
list.add(new Person(3,"John",18));
list.add(new Person(2,"Marry",21));
list.add(new Person(2,"Tom",20));
list.add(new Person(4,"Mark",20));
list.add(new Person(4,"Ruby",20));
System.out.println("Before sort:");
printList(list);
Collections.sort(list);
System.out.println("After sort:");
printList(list);
}
public static void printList(List<Person> list){
for (Person p : list){
System.out.print(p + " / ");
}
System.out.println();
}
}
輸出結(jié)果:
Before sort:
id: 3 name: John age: 18 / id: 2 name: Marry age: 21 / id: 2 name: Tom age: 20 / id: 4 name: Mark age: 20 / id: 4 name: Ruby age: 20 /
After sort:
id: 2 name: Marry age: 21 / id: 2 name: Tom age: 20 / id: 3 name: John age: 18 / id: 4 name: Mark age: 20 / id: 4 name: Ruby age: 20 /
我們可以看到首先根據(jù)id進(jìn)行升序排序,當(dāng)id相同時(shí),根據(jù)age進(jìn)行降序排序,如Marry和Tom,若id和age都相同,則對(duì)name進(jìn)行升序排序,如Mark和Ruby。
實(shí)現(xiàn)Comparable接口所需滿足的需求:
- 滿足對(duì)稱性:必須確保所有的x和y都滿足sgn(x.compareTo(y)) == -sgn(y.compareTo(x));
- 滿足傳遞性:若(x.compareTo(y) >0 && y.compareTo(z)>0),則x.compareTo(z)>0。
- 若x.compareTo(y)==0,則sgn(x.compareTo(z)) == sgn(y.compareTo(z));