• 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 ---祥子

  • 相关阅读:
    Example [mybatis] 的用法
    开发中可能会用到的几个 jQuery 小提示和技巧
    setInterval 与 clearInterval详解
    15个华丽的扁平风格登录界面
    原生js模拟jquery写法
    纯色扁平化网站
    javascript刷新页面的集中办法
    javascript闭包中循环问题
    20个实用javascript技巧及实践(二)
    20个实用的javascript技巧及实践(一)
  • 原文地址:https://www.cnblogs.com/stephenzengx/p/13679695.html
Copyright © 2020-2023  润新知