Java比較器:Comparator接口與Comparable接口的compare(compareTo)方法返回值的正負與升序、降序的關系


在Java中經常會涉及到多個對象的排序問題,那么就涉及到對象之間的比較。

Java實現對象排序的方式有兩種:

自然排序:java.lang.Comparable

定制排序:ava.util.Comparator

1. 自然排序:java.lang.Comparable

  • Comparable 接口強行對實現它的每個類的對象進行整體排序。這種排序被稱為類的自然排序。 Comparable 接口中只有一個抽象方法:int compareTo(Object o);

  • 實現 Comparable 的類必須實現 compareTo(Object o) 方法,兩個對象即通過 compareTo(Object o) 方法的返回值來比較大小。

    • 如果當前對象 this 大於形參對象 o ,則返回正整數,
    • 如果當前對象 this 小於形參對象 o ,則返回負整數,
    • 如果當前對象 this 等於形參對象 o ,則返回零。
  • 實現 Comparable 接口的類的對象數組(和有序集合)可以通過 Arrays.sort(和 Collections.sort )進行自動排序。

  • Comparable的典型實現:(默認都是從小到大排序

    String、包裝類等實現了Comparable接口,重寫了compareTo(obj)方法

    String:按照字符串中字符的Unicode值進行比較
    數值類型對應的包裝類以及BigInteger、BigDecimal:按照它們對應的數值大小進行比較
    Character:按照字符的Unicode值來進行比較
    Boolean:true 對應的包裝類實例大於 false 對應的包裝類實例
    Date、Time等:后面的日期時間比前面的日期時間大

    代碼示例:

    String[] arr = new String[]{"AA","cc","ac","dd","aa","FF","ff"};
    Arrays.sort(arr);
    System.out.println(Arrays.toString(arr)); // [AA, FF, aa, ac, cc, dd, ff]
    
  • 對於自定義類來說,如果需要排序,我們可以讓自定義類實現 Comparable 接口,重寫 compareTo(Object o) 方法。在 compareTo(Object o) 方法中指明如何排序。

    定義學生類:

    public class Student {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    測試類:

    public class ComparableTest {
        public static void main(String[] args) {
            Student[] students = new Student[5];
            students[0] = new Student("rose", 16);
            students[1] = new Student("jack", 18);
            students[2] = new Student("mark", 16);
            students[3] = new Student("john", 16);
            students[4] = new Student("lily", 17);
    
            Arrays.sort(students);
    
            for (int i = 0; i < students.length; i++) {
                System.out.println(students[i]);
            }
        }
    }
    

    發現程序出現了類型轉換異常:

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ggMQjfNv-1598369086597)(img/Comparable.png)]

    原因:對於自定義類來說,如果需要排序,自定義類必須實現 Comparable 接口,重寫 compareTo(Object o) 方法。在 compareTo(Object o) 方法中指明如何排序。

    修改Student類,實現 Comparable 接口,指定按學生年齡升序排序:

    public class Student implements Comparable {
        ...
        
        @Override
        public int compareTo(Object o) {
     		Student student = (Student) o;
            if (this.age < student.age) {
                return -1;
            } 
            if (this.age > student.age) {
                return 1;
            }
            return 0;
        }
    }
    

    運行結果:

    Student{name='mark', age=15}
    Student{name='rose', age=16}
    Student{name='john', age=16}
    Student{name='lily', age=17}
    Student{name='jack', age=18}

    如果要按學生年齡降序排序,則修改compareTo方法:當前對象的年齡小於參數對象時,返回正整數;當前對象大於參數對象時,返回負整數:

    @Override
    public int compareTo(Object o) {
        Student student = (Student) o;
        if (this.age < student.age) {
            return 1;
        } 
        if (this.age > student.age) {
            return -1;
        }
        return 0;
    }
    

2. 定制排序:java.util.Compartor

  • 當元素的類型沒有實現 java.lang.Comparable 接口而又不方便修改代碼,或者實現了java.lang.Comparable接口的排序規則不適合當前的操作,那么可以考慮使用 Comparator 接口的實現類來排序。

  • Comparator 接口中只有兩個抽象方法 int compare(Object o1, Object o2);boolean equals(Object obj);Comparator 接口實現類默認繼承了 Object 類的 equals 方法,即間接實現了 equals 方法,因此只需實現 int compare(Object o1, Object o2) 即可。

  • 可以將 Comparator 接口實現類傳遞給 sort 方法(如 Arrays.sort 或 Collections.sort),從而允許在排序順序上實現精確控制。

  • 重寫 int compare(Object o1, Object o2) 方法,比較o1和o2的大小:

    如果要按照升序排序,
    則 o1小於o2返回負整數,o1與o2相等返回0,01大於02返回正整數

    如果要按照降序排序
    則 o1小於o2返回正整數,o1與o2相等返回0,01大於02返回負整數

    代碼演示:

    定義Student類,無需實現Comparable接口:

    public class Student {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    測試類:向sort方法中傳入待排序的數組對象和Comparator接口實現類,指定按學生年齡升序排序:

    public class ComparableTest {
        public static void main(String[] args) {
            Student[] students = new Student[5];
            students[0] = new Student("rose", 16);
            students[1] = new Student("jack", 18);
            students[2] = new Student("mark", 15);
            students[3] = new Student("john", 16);
            students[4] = new Student("lily", 17);
    
            Arrays.sort(students, new Comparator() {
                @Override
                public int compare(Object o1, Object o2) {
                    Student s1 = (Student) o1;
                    Student s2 = (Student) o2;
                    //下面的代碼可簡化為 return s1.getAge() - s2.getAge();
                    if (s1.getAge() < s2.getAge()) {
                        return -1;
                    }
                    if (s1.getAge() > s2.getAge()) {
                        return 1;
                    }
                    return 0;
                }
            });
    
            for (int i = 0; i < students.length; i++) {
                System.out.println(students[i]);
            }
        }
    
    }
    

    運行結果:

    Student{name='mark', age=15}
    Student{name='rose', age=16}
    Student{name='john', age=16}
    Student{name='lily', age=17}
    Student{name='jack', age=18}

    如果要按學生年齡降序排序,則修改compare方法:當前對象的年齡小於參數對象時,返回正整數;當前對象大於參數對象時,返回負整數:

    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        //下面的代碼可簡化為 return s2.getAge() - s1.getAge();
        if (s1.getAge() < s2.getAge()) {
            return 1;
        }
        if (s1.getAge() > s2.getAge()) {
            return -1;
        }
        return 0;
    }
    

    按年齡降序排序,年齡相同時按姓名首字母升序排序:

    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        //第一條排序規則:年齡降序
        int result = s2.getAge() - s1.getAge();
        //第二條排序規則:年齡相同時,姓名首字母升序
        if (result == 0) {
            result = s1.getName().charAt(0) - s2.getName().charAt(0);
        }
        return result;
    }
    

    運行結果:

    Student{name='jack', age=18}
    Student{name='lily', age=17}
    Student{name='john', age=16}
    Student{name='rose', age=16}
    Student{name='mark', age=15}

3. 比較方法的返回值正負與升序、降序的關系

從前面已經知道:如果要按照升序排序,則 o1小於o2返回負整數,o1與o2相等返回0,01大於02返回正整數

如果要按照降序排序,則 o1小於o2返回正整數,o1與o2相等返回0,01大於02返回負整數

那么,對元素升序、降序排序時調整元素位置與接口中返回值的正負有什么關系呢?

測試環境:JDK8

原數組元素順序:

Student{name='rose', age=16}
Student{name='jack', age=18}
Student{name='mark', age=15}
Student{name='john', age=16}
Student{name='lily', age=17}
  • 在compare方法中,不管o1、o2對象的內容,統一返回正整數:
@Override
public int compare(Object o1, Object o2) {
    return 1;
}

運行結果發現,數組元素順序未改變。

  • 在compare方法中,不管o1、o2對象的內容,統一放回負整數,運行結果發現,數組元素倒置。
  • 在compare方法中,不管o1、o2對象的內容,統一放回0,運行結果發現,數組元素順序未改變。

在comparable接口中及集合中的運行結果一致。

從而得出結論:在調用 compare(Object o1, Object o2) 方法時,會把兩個元素中索引較小的元素賦值給o2,索引較大的元素賦值給o1。Comparable接口中compareTo(Object o) 方法的調用者是兩個元素中索引較大的元素,參數對象 o 則是索引較小的元素。(JDK版本不同,對應關系可能不同)。通過debug調試或查看源碼也能得出這個結論。

比如,對於數組{5, 10, 20, 15},比較前兩個元素調用 compare(Object o1, Object o2) 方法時,會把5賦值給o2,把10賦值給o1。按升序排序時,若o1 > o2,則返回正整數,不調整元素位置;若o1 == o2,則返回0,不調整元素位置;若o1 < o2,則返回負整數,調整元素位置。按降序排序時,若o1 > o2,則返回負整數,需要調整元素位置;若o1 == o2,則返回0,不調整元素位置;若o1 < o2,則返回正整數,不調整元素位置。因此,不管是升序還是降序,只有在返回負整數時,才會調整元素的位置。

綜上,排序時是否需要調整元素位置是由對象o1、o2與數組中元素的對應關系 和 比較方法返回值的正負共同決定的。

4. Comparable接口和Comparator接口的區別

只要實現Comparable 接口的對象直接就成為一個可以比較的對象,但是需要修改源代碼。

用Comparator 的好處是不需要修改源代碼, 而是在待比較對象的類的外部實現一個比較器, 當某個自定義的對象需要作比較的時候,把待比較對象和比較器一起傳遞過去就可以實現排序功能。

此外,像String類、包裝類等JDK內置類實現了Comparable接口默認是升序排序,如果要降序排序或指定其他排序規則只能使用Comparator接口。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM