Java comparable接口及compareTo返回值所決定的升序降序問題


我們在學習java基礎的時候知道,基本數據類型數組可以直接調動Arrays類的靜態sort方法,然后輸出。

例如: int iArr[] = {1,2,4,6};  Arrays.sort(iArr); 然后利用for循環輸出..

 

但是如果我們是對象數組的話,則對象所在的類必須實現comparable接口,覆寫它的compareTo方法,並且要有不同的返回值,代表升序和降序。

但是會有一個疑問:為什么對象數組調用Arrays.sort 必須要實現comparable接口呢?而且compareTo的返回值到底代表什么意義呢,它又是如何決定數組的升序和降序的呢?

帶着這樣的疑問,我自己寫了一個很簡單的例子,然后進行調試,跟進源碼,終於發現的一點端倪,然后把我自己理解分享給大家

關於如何進入源碼,以及查看源碼中與變量有關的信息我的有一篇博客講到 補充 ,有時間可以去看下。。

 

接下來上代碼://Test類 package TEST;

 public class Test implements Comparable<test>
 { 
    int age = 0; 
    String Name; 
    public Test(int age, String name) 
    { 
        super(); 
        this.age = age; 
        Name = name; 
    } 

    public int compareTo(Test o) 
    { 
        if (this.age > o.age)
          return 1; 
        else return -1; 
    }
    public String toString()
    { 
        return age+Name; 
    } 
 } 

  

//TestDemo類 package TEST;
import java.util.Arrays; 
public class TestDemo 
{ 
    public static void main(String args[]) 
    { 
        Test t[] = {
            new Test(6, "paul"), 
            new Test(5, "ss"), 
            new Test(2, "kk"), 
            new Test(3, "tt"), 
            new Test(1, "ii")
        }; 
        Arrays.sort(t); 
        for (int i=0; i<t.length; i++) 
        { 
            System.out.println(t[i]); 
        } 
    } 
}
                                          

接下來我們加入斷點進行調試

首先我們進入方法1: Arrays.class中的sort()方法

public static void sort(Object[] a) 
{ 
  if (LegacyMergeSort.userRequested) 
    legacyMergeSort(a); 
  Else 
    ComparableTimSort.sort(a, 0, a.length, null, 0, 0); 
}

接下來進入方法2:ComparableTimSort.class 的sort()方法

static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) 
{ 
    assert a != null && lo >= 0 && lo <= hi && hi <= a.length; 
    int nRemaining = hi - lo; 
    if (nRemaining < 2) return; 
    // Arrays of size 0 and 1 are always sorted 
    // If array is small, do a "mini-TimSort" with no merges 
    if (nRemaining < MIN_MERGE) 
    { 
        int initRunLen = countRunAndMakeAscending(a, lo, hi); 
        binarySort(a, lo, hi, lo + initRunLen); 
        return; 
    } 
        
    ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen); 
    int minRun = minRunLength(nRemaining); 
    
    do { 
        // Identify next run 
        int runLen = countRunAndMakeAscending(a, lo, hi); 
        // If run is short, extend to min(minRun, nRemaining) 
        if (runLen < minRun) 
        { 
            int force = nRemaining <= minRun ? nRemaining : minRun;     
            binarySort(a, lo, lo + force, lo + runLen); 
            runLen = force; 
        } 
        // Push run onto pending-run stack, and maybe merge ts.pushRun(lo, runLen); 
        ts.mergeCollapse(); 
        // Advance to find next run 
        lo += runLen; nRemaining -= runLen; 
    } while (nRemaining != 0); 
    // Merge all remaining runs to complete sort 
    assert lo == hi; 
    ts.mergeForceCollapse(); 
    assert ts.stackSize == 1; 
}

 

下一步進入:方法3:ComparableTimSort.class的countRunAndMakeAscending方法里面終於見到了我們的CompareTo方法

    private static int countRunAndMakeAscending(Object[] a, int lo, int hi) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;
        // Find end of run, and reverse range if descending
        if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0) { 
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)
                runHi++;
            //反轉對象數組的lo~runHi-1部分,該方法也在ComparableTimSort.class中
            reverseRange(a, lo, runHi); 
        } else {                              
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) >= 0)
                runHi++;
        }


       return runHi - lo;
    }

這里你應該終於明白了為什么一定要實現comparable接口中的compareTo方法了吧。

如何你沒有實現,那么通過接口去找compareTo方法肯定會報錯啊(因為這時候找不到compareTo方法,未定義)

結合Test類中的compareTo方法,當this.age<o.age時,返回-1;

所以進入while循環,直到 a[runHi]).compareTo(a[runHi - 1])>=0時結束循環,后反轉對象數組的lo~runHi-1部分

(ComparableTimSort.class)reverseRange(a, lo, runHi)的源碼如下:

    private static void reverseRange(Object[] a, int lo, int hi) {
        hi--;
        while (lo < hi) {
            Object t = a[lo];
            a[lo++] = a[hi];
            a[hi--] = t;
        }
    }

此時經過反轉后數組變為了 2, 5, 6, 3, 1(這里沒有寫name屬性),也就是前面3個對象是有序的,升序接下來我們就進入了下一個方法(ComparableTimSort.class)

 private static void binarySort(Object[] a, int lo, int hi, int start) 
 { 
    assert lo <= start && start <= hi; 
    if (start == lo) start++; 
    for ( ; start < hi; start++) 
    { 
        Comparable pivot = (Comparable) a[start]; 
        // Set left (and right) to the index where a[start] (pivot) belongs 
        int left = lo; 
        int right = start; 
        assert left <= right; 
    
        /*  Invariants: * pivot >= all in [lo, left). * pivot < all in [right, start). */ 
        while (left < right) 
        { 
            int mid = (left + right) >>> 1; 
            if (pivot.compareTo(a[mid]) < 0) 
                right = mid; 
            else 
               left = mid + 1; 
        } 

        assert left == right; 
        int n = start - left; 
        // The number of elements to move 
        // Switch is just an optimization for arraycopy in default case 
        switch (n) 
        { 
            case 2: a[left + 2] = a[left + 1]; 
            case 1: a[left + 1] = a[left]; break; 
            default: System.arraycopy(a, left, a, left + 1, n); 
        } 
        a[left] = pivot; 
    } 
 }

  

我花了一點時間看了一下,就是C語言數據結構的二分排序法(也叫折半插入法,它是插入排序的一種改進版)

基本思想是:現將部分數組的部分元素變成一個有序的數組,后面的元素通過折半插入將它變成一個有序的數組。

例如前文:數組變成了2,5,6,3,1

則3插入前面有序的數組中,變成了 2,3,5,6,1

1在插入前面有序的數組中,變成了1,2,3,5,6 

大家有時間可以去研究下。。這里不做詳細說明。到了這里方法基本都跟進並介紹完了

輸出結果:

1ii
2kk
3tt
5ss
6paul  升序。

那如何是降序呢?

修改Test類中的compareTo方法:

public int compareTo(Test o) 
{
    if (this.age > o.age)
        return -1;
     
    return 1;
}

將返回值調換就行了,輸出結果:

6paul
5ss
3tt
2kk
1ii    降序

關於compareTo方法的實現及返回值以下的組合,譬如:

public int compareTo(Test o) 
{
    if (o.age > this.age){
        return 1;
    
    return -1;
}  

public int compareTo(Test o)
{
  if (o.age > this.age)
    return -1;
  
  return 1;
}  

那他們到底是升序還是降序呢?自己結合源碼可以去思考一下。

但是上面兩種不建議寫,因為容易混淆。推薦寫最上面兩種。。。

 

寫了這么多 總結一下:

//升序
public int compareTo(Test o) 
{
    if (this.age>o.age ){
        return 1;
    
    return -1;
}  

//降序
public int compareTo(Test o)
{
  if (this.age > o.age)
    return -1;
  
  return 1;
}  

我自己記憶的方法是:

大於號 返回1,正乘正為正,所以升序(可以把>號想象為正)

大於號返回-1,正乘負為負,所以降序

 

提示非常重要的一點,compareTo中的方法一定要有至少兩個以上(其實兩個足夠)的返回值,而且一個返回值一定要小於0,另一個一定要大於或等於0。

否則排序不會成功。自己結合ComparableTimSort.class的countRunAndMakeAscending方法分析。

 

最后留一個問題:我們說可以按照年齡屬性進行降序升序排序,但比如有如下要求。

要求按照年齡大小升序排列,當年齡相同時,按照name屬性降序排列,這時候compareTo函數怎么寫呢?

大家可以去思考一下,這里我就不貼代碼出來了,相信大家看過前文,自己思考一下應該可以寫出來。

 

好了,這個博客寫了好多小時了,該結束了,現在是北京時間22:25。撤了,撤了,大家晚安。

                                                                                                        2017/6/19/ 22:25 ---祥子


免責聲明!

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



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