首页 新闻 会员 周边 捐助

Comparison method violates its general contract!

0
悬赏园豆:80 [待解决问题]

首先,代码如下:

List<Integer> favPros = userProviderDAO.getFavorProviderByUserId(processedParams.getUserIdInt(), 0, 9999);
        if (!id2num.isEmpty()) {
            Iterable<Integer> ids = id2num.keySet();
            Map<Integer, Map<String, Object>> id2order = providerDAO.getProvidersListOrderByIds(ids, processedParams.getCityId());
            for (Provider p : providerList) {
                if (id2order.containsKey(p.getId())) {
                    p.setListorder((int) id2order.get(p.getId()).get("listorder"));
                }
            }
            // 关注商家排前
            providerList.sort(Comparator
                    .comparingInt((Provider p) -> favPros.contains(p.getId()) ? favPros.indexOf(p.getId()) : favPros.size())
                    .thenComparingInt(Provider::getListorder));
            
// Provider类里Listorder是int类型

错误如下:

java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.TimSort.mergeHi(TimSort.java:899) ~[?:1.8.0_181]
    at java.util.TimSort.mergeAt(TimSort.java:516) ~[?:1.8.0_181]
    at java.util.TimSort.mergeForceCollapse(TimSort.java:457) ~[?:1.8.0_181]
    at java.util.TimSort.sort(TimSort.java:254) ~[?:1.8.0_181]
    at java.util.Arrays.sort(Arrays.java:1438) ~[?:1.8.0_181]
    at java.util.List.sort(List.java:478) ~[?:1.8.0_181]
    ... ...

问题描述:

1.此错误出现在正式服,不时就会报错,没办法debug,且用日志中的接口参数和路径,并使用postman多次重新请求,发现报错的都可以成功,改错误无法重现。

2.网上一堆说自己实现的compare不呵护规范,排序逻辑不严谨,而此处我使用的是Jdk8的方法,并没有自己实现compare方法;网上说传入的值可能为null,然后在compare方法里进行判断,同上,而且此处我传入的值不可能为null,因为两个都是int,都有默认值。

3.我是在想不通哪里有问题,这不是java8里接口的常见使用吗?而且官方发布的指导资料也是这样使用的,我不相信java8有这么大的漏洞。

4.请大家提供点思路,谢谢!

jasmhusc的主页 jasmhusc | 初学一级 | 园豆:48
提问于:2020-03-16 17:46
< >
分享
所有回答(2)
0

这个错误一般是由于compare写的不规范导致的。
当对两个数据进行比较时,结果应该是永远不变的,即:A和B比较,或者B和A比较,结果应该完全一致。
举个错误的例子:
compare: (o1, o2) -> return o1 > o2 ? 1 : -1;
假设A = B,那么比较A和B时,结果是A<B。比较B和A时,结果是B<A。
这样排序时出现了结果的不确定,可能会抛出这个异常。

回到你这个例子,你没有自己写compare,而是使用的jdk提供的compareInt,那么问题可能出在哪儿呢?
我分析还是结果的不确定,只不过不确定的因素不是compare方法了,而是你传给compareInt的值不是固定的。

        providerList.sort(Comparator
                .comparingInt((Provider p) -> favPros.contains(p.getId()) ? favPros.indexOf(p.getId()) : favPros.size())
                .thenComparingInt(Provider::getListorder));

集中到你这段代码,providerList有修改时,会出现这个错误。你是否是多线程有修改这个list?
favPros 从代码里看是局部变量,应该没有问题,getListorder应该也没有问题。所以我建议你去看看providerList这个变量,如果要排序,那么要保证这个list的元素,在排序过程中,是固定不变的。

。淑女范erり | 园豆:961 (小虾三级) | 2020-03-19 19:42
// 创建对象
List<Provider> providerList = new LinkedList<>();
        Map<Integer, Long> id2num = new HashMap<>();
        long bknum = 0;

        Map<Integer, Long> providerId2Count = getFacetResultInt(response, "effectiveGroup");
        Map<Integer, Provider> allProviders = providerService.getProviderByIds(providerId2Count.keySet());
        for (Map.Entry<Integer, Long> facetValues : providerId2Count.entrySet()) {
            int id = facetValues.getKey();
            Provider p = allProviders.get(id);
            if (p.getProvider_type() == 0) {
                bknum += facetValues.getValue();
            } else {
                id2num.put(id, facetValues.getValue());
                // 添加数据:Provider对象
                providerList.add(p);
            }
        }
        long t3 = System.currentTimeMillis();
        List<Integer> favPros = userProviderDAO.getFavorProviderByUserId(processedParams.getUserIdInt(), 0, 9999);
        if (!id2num.isEmpty()) {
            Iterable<Integer> ids = id2num.keySet();
            Map<Integer, Map<String, Object>> id2order = providerDAO.getProvidersListOrderByIds(ids, processedParams.getCityId());
            for (Provider p : providerList) {
                if (id2order.containsKey(p.getId())) {
                    // 给属性ListOrder赋值
                    p.setListorder((int) id2order.get(p.getId()).get("listorder"));
                }
            }
            // 集合排序
            providerList.sort(Comparator
                    .comparingInt((Provider p) -> favPros.contains(p.getId()) ? favPros.indexOf(p.getId()) : favPros.size())
                    .thenComparingInt(Provider::getListorder));
        }

感谢你的回复,你的想法很有启发意义,我也感觉可能是一些不易察觉的原因导致的,你说的多线程我其实用的很少,这部分代码是一个web的service层的接口里面的方法,该service是注入到spring的,应该没有多线程吧?而且你可以看见,providerList是主动new出来的,然后根据条件判断往里add了些数据,再然后根据map的映射关系,设置了Provider对象的ListOrder属性值,再然后就是比较排序了。

支持(0) 反对(0) jasmhusc | 园豆:48 (初学一级) | 2020-03-19 20:13

@jasmhusc: 单从你这段代码来看,确实不好看出原因。
providerList 为局部变量就不涉及多线程修改的问题了。

只能凭空猜测,你的DAO或者service读取Providor数据时有什么问题。
favPros
allProviders
这两个集合取值的方法那里,是不是读取的缓存数据?

如果可以,观察下出错前后是否有provider 或者 fav相关的数据更新。

支持(0) 反对(0) 。淑女范erり | 园豆:961 (小虾三级) | 2020-03-20 09:45

@。淑女范erり:

providerList = providerList.stream().map(x -> {
                int idx = favPros.indexOf(x.getId());
                idx = idx >= 0 ? idx : favPros.size();
                return Tuples.of(idx, x.getListorder(), x);
            }).sorted(Comparator.comparingInt(x -> (int) ((Tuple3) x).getT1()).thenComparingInt(x -> (int) ((Tuple3) x).getT2()))
                    .map(Tuple3::getT3).collect(Collectors.toList());
        }

同事已经修改为元组的逻辑了,你说的数据都是直接执行的sql,并没有涉及缓存,新代码效果等上线才知道,到时候再回复你,谢谢你的建议。

支持(0) 反对(0) jasmhusc | 园豆:48 (初学一级) | 2020-03-26 10:07

如前所述,集合排序逻辑改为先将数据一一放到三元组里,再对元组里的元素进行排序,在正式服部署上线之后,一个星期以来没有再出现排序的错误和异常,后续再关注一下,如果依然没有问题,表明问题已经解决,只是原因至今没有找到。

支持(0) 反对(0) jasmhusc | 园豆:48 (初学一级) | 2020-04-20 11:40
0

如前所述,集合排序逻辑改为先将数据一一放到三元组里,再对元组里的元素进行排序,在正式服部署上线之后,一个星期以来没有再出现排序的错误和异常,后续再关注一下,如果依然没有问题,表明问题已经解决,只是原因至今没有找到。

jasmhusc | 园豆:48 (初学一级) | 2020-04-20 11:41
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册