阿里Java手册剖析-6.15【强制】在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort, Collec

1,705 阅读3分钟

阿里Java开发手册剖析:


【强制】在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort, Collections.sort 会抛 IllegalArgumentException 异常。

说明:三个条件如下

  1. x,y 的比较结果和 y,x 的比较结果相反。
  2. x>y,y>z,则 x>z。
  3. x=y,则 x,z 比较结果和 y,z 比较结果相同。

其实主要漏洞是,我们在比较大小时,忘记了处理相等的情况。比如下面这种写法:


@Override
    public int compareTo(Person o) {
        return this.age > o.age? 1 : -1;
    }

如何过传入一个age相同的Person对象,会返回-1;所以他会排到前面的位置,认为是小的对象。 那么为啥JDK7之后才需要这么注意呢?

因为JDK7之后sort由TimeSort来实现,里面采用了一套新的排序算法。这套算法就需要处理好相等的额情况。

java.lang.ClassCastException: xxxxx cannot be cast to java.lang.Comparable

我们可以通过Arrays.sort, Collections.sort对数组和集合进行排序。

但是这个对象必须是实现了Comparable<T>接口。

看下面这个反面例子:

public class sort {

    public static void main(String[] args) {

        Person[] persons = {
                new Person(10, 1, "yang"),
                new Person(11, 1, "ge"),
                new Person(12, 0, "peng"),
        };

        Arrays.sort(persons);

        for (Person person : persons) {
            System.out.println(person.toString());
        }
    }


}

class Person {
    int age;
    int sex;
    String name;

    public Person(int age, int sex, String name) {
        this.age = age;
        this.sex = sex;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", sex=" + sex +
                ", name='" + name + '\'' +
                '}';
    }
}

日志输出:

根据日志提示可以看到。Arrays.sort(Object[] a)是通过ComparableTimSort。追踪源码可见:


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

ComparableTimSort-->

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;
        }

        ...
    }

private static int countRunAndMakeAscending(Object[] a, int lo, int hi) {
        ...
        if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0) { // Descending
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              // Ascending
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) >= 0)
                runHi++;
        }

        return runHi - lo;
    }

countRunAndMakeAscending(Object[] a, int lo, int hi)会对传入的数组元素强转为Comparable:if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0)

所以传入的sort的对象元素必须要实现Comparable<T>接口。 将上面的Person类修改如下再运行就没问题了:


class Person implements Comparable<Person> {
    int age;
    int sex;
    String name;

    public Person(int age, int sex, String name) {
        this.age = age;
        this.sex = sex;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", sex=" + sex +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public int compareTo(Person o) {
        return this.age -o.age ;
    }
}

日志输出如下:

Person{age=10, sex=1, name='yang'}
Person{age=11, sex=1, name='ge'}
Person{age=12, sex=0, name='peng'}

Process finished with exit code 0

可以看到是按照年级从小到大排序。如果我们修改下public int compareTo(Person o)内实现:

@Override
    public int compareTo(Person o) {
        return o.age - this.age;
    }

日志输出:

Person{age=12, sex=0, name='peng'}
Person{age=11, sex=1, name='ge'}
Person{age=10, sex=1, name='yang'}

Process finished with exit code 0

就看到按照年级从大到小排。

所以:public int compareTo(Person o)的返回值决定了排序的顺序。


public interface Comparable<T> {
   
   / *
     * @param   o 比较的对象
     * @return  返回一个负数、0、正数,分别代表着小于、等于、大于指定比较对象
     *
     */
    public int compareTo(T o);
}

IllegalArgumentException