首先,代码如下:
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.请大家提供点思路,谢谢!
这个错误一般是由于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的元素,在排序过程中,是固定不变的。
// 创建对象
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属性值,再然后就是比较排序了。
@jasmhusc: 单从你这段代码来看,确实不好看出原因。
providerList 为局部变量就不涉及多线程修改的问题了。
只能凭空猜测,你的DAO或者service读取Providor数据时有什么问题。
favPros
allProviders
这两个集合取值的方法那里,是不是读取的缓存数据?
如果可以,观察下出错前后是否有provider 或者 fav相关的数据更新。
@。淑女范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,并没有涉及缓存,新代码效果等上线才知道,到时候再回复你,谢谢你的建议。
如前所述,集合排序逻辑改为先将数据一一放到三元组里,再对元组里的元素进行排序,在正式服部署上线之后,一个星期以来没有再出现排序的错误和异常,后续再关注一下,如果依然没有问题,表明问题已经解决,只是原因至今没有找到。
如前所述,集合排序逻辑改为先将数据一一放到三元组里,再对元组里的元素进行排序,在正式服部署上线之后,一个星期以来没有再出现排序的错误和异常,后续再关注一下,如果依然没有问题,表明问题已经解决,只是原因至今没有找到。