Collections.sort()崩溃分析

对List排序一般采用两种方法:
(1)实体类实现Comparable<T>接口,完成compareTo(T o)方法。
(2)创建一个Comparator<T>的实现类,完成compare(T o1, T o2)方法,然后利用这个比较器对List进行排序。
详情可以看一下我的这篇文章
当我们书写compareTo(T o)compare(T o1, T o2)方法时,我们知道:当第一个参数小于第二个参数时返回负整数;当第一个参数等于第二个参数时返回0;当第一个参数大于第二个参数时返回正整数。
但一定要记住3个原则:
1. 确保:sgn(compare(x, y)) == -sgn(compare(y, x)).
2. 确保:如果((compare(x, y)>0) && (compare(y, z)>0)),那么compare(x, z)>0.
3. 确保:如果compare(x, y)==0,那么对于任意的z都有sgn(compare(x, z))==sgn(compare(y, z))成立.

sgn(expression)是一个数学正负号函数,根据expression是负数、0、正数分别返回-1、0、1。

可以不实现equals()方法,也不一定要求(compare(x, y)==0) == (x.equals(y))。但建议正确实现各个方法。

由于JDK7之前Collections.sortArrays.sort所使用的排序算法并没有对此做严格的限制,所以可以在x、y相等的时候不返回0,但JDK7之后采用更高效的TimSort算法进行排序,就需要严格遵循上面的3个原则,否则就会出现下面这样的异常:

Exception in thread “main” java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(Unknown Source)
at java.util.TimSort.mergeAt(Unknown Source)
at java.util.TimSort.mergeForceCollapse(Unknown Source)
at java.util.TimSort.sort(Unknown Source)
at java.util.Arrays.sort(Unknown Source)
at java.util.ArrayList.sort(Unknown Source)
at java.util.Collections.sort(Unknown Source)
at cn.test.JDK7.main(JDK7.java:32)

该异常并不是总会出现,只有当集合中元素顺序影响到算法排序时才会出现(如TimSort在Merge过程中出触发了“胜利N次”优化,并且A[base1]==A[base1+x])&&(A[base1+x]==B[base2]时,具体可以看一下源码中mergeLo()和mergeHi()方法的实现),所以在开发环境下很难复现这个异常,通常只会在产品环境下发现。所以为了遵循原则,也是为了养成良好的编程习惯,一定要正确实现比较器。
下面是一个会出现异常的Case:

package cn.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class JDK7 {

    private static final String SORT_TIME_VALUE = "sort_time_value";
    private static final String PRICE_DOLLAR = "price_dollar";
    private static ArrayList<GameBean> gameBeans = new ArrayList<>();

    public static void main(String[] args) {
        add("0", 7);
        add("3", 1);
        add("0", 25);
        add("1", 2);
        add("0", 2);
        add("1", 1);
        add("0", 1);
        add("1", 1);
        add("0", 4);
        add("1", 1);
        add("0", 2);
        add("1", 1);
        add("0", 3);
        add("2", 1);
        add("1", 1);
        Collections.sort(gameBeans, new GameComparator(SORT_TIME_VALUE, 1));
        for (GameBean gameBean : gameBeans) {
            System.out.println(gameBean.getTime());
        }
    }

    private static void add(String time, int count) {
        for (int i = 0; i < count; i++) {
            GameBean gameBean0 = new GameBean();
            gameBean0.setTime(time);
            gameBean0.setPrice(time);
            gameBeans.add(gameBean0);
        }
    }

    static class GameComparator implements Comparator<GameBean> {

        String mSortField;
        int mSortType;// 1:升序 -1:降序

        public GameComparator(String sortField) {
            this(sortField, 1);
        }

        public GameComparator(String sortField, int sortType) {
            this.mSortField = sortField;
            this.mSortType = sortType;
        }

        @Override
        public int compare(GameBean lhs, GameBean rhs) {
            int ret = 0;
            if (SORT_TIME_VALUE.equals(mSortField)) {
                Long x = Long.valueOf(lhs.getTime());
                Long y = Long.valueOf(rhs.getTime());
                ret = mSortType * x.compareTo(y);
            } else if (PRICE_DOLLAR.equals(mSortField)) {
                Float x = Float.valueOf(lhs.getPrice());
                Float y = Float.valueOf(rhs.getPrice());
                ret = mSortType * x.compareTo(y);
            }
            return ret;
            // return (Integer.valueOf(lhs.getTime()) > Integer.valueOf(rhs.getTime())) ? 1 : -1;
        }
    }

    static class GameBean {

        private String time;
        private String price;

        public String getTime() {
            return time;
        }

        public void setTime(String time) {
            this.time = time;
        }

        public String getPrice() {
            return price;
        }

        public void setPrice(String price) {
            this.price = price;
        }

    }

}

注意:

  • 不要用三目运算符返回. o1 > o2 ? 1 : -1没有考虑相等的情况,如果o1等于o2就违背了3条原则。
  • 不要将long强转成int. 如果long超过了int最大值会导致数据错误,进而影响排序。
  • 不要忽略比较的传递性. 尤其是比较逻辑很复杂时。

想了解更多,可以看一下下面几篇文章:

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值