ArrayList.sort()抛出IllegalArgumentException异常原因排查
嘿,这个问题我之前踩过坑!核心原因是你的比较器违反了Java比较器必须遵守的通用契约,尤其是自反性、对称性和传递性规则。Java 7及以后的Arrays.sort/Collections.sort改用了TimSort算法,它会严格校验比较器的契约,不像旧算法那样“宽容”,所以一旦违反就会直接抛出IllegalArgumentException。
你的原比较器哪里出问题了?
先看你写的比较器逻辑:
(lineItem1, lineItem2) -> { if (lineItem1 && lineItem2) { return 0; } else if (!lineItem1) { return 1; } else if (!lineItem2 ) { return -1; } return 0; }
这里有个致命漏洞:当两个元素都是false时,你的比较器会返回1。比如false.compareTo(false)返回1,这直接违反了两个核心规则:
- 自反性:
x.compareTo(x)必须返回0(自己和自己比较肯定相等); - 对称性:如果
a.compareTo(b) > 0,那么b.compareTo(a)必须< 0(如果a大于b,那b必然小于a)。但你这里false.compareTo(false)=1,反过来还是1,完全矛盾。
再用传递性验证:假设我们有三个元素A=true、B=false、C=false:
A.compareTo(B)返回-1(A < B)B.compareTo(C)返回1(B > C)A.compareTo(C)返回-1(A < C)
TimSort在合并排序片段时会发现逻辑矛盾:按照前两个比较结果,A < B且B > C,A和C的关系本应不确定,但你的比较器却强制A < C,加上B和C之间的错误比较,最终触发了契约违反的异常。
为什么调换return值后暂时不抛异常?
当你把return 1和return -1调换后,比较器变成:
(lineItem1, lineItem2) -> { if (lineItem1 && lineItem2) { return 0; } else if (!lineItem1) { return -1; } else if (!lineItem2 ) { return 1; } return 0; }
这时候两个false比较会返回-1,也就是false < false,同样违反自反性和对称性,但为什么没抛异常?
这是因为你的测试用例里只有1个true,剩下的都是false。调换后的比较器会把true排在最后,而所有false之间的比较都返回-1,相当于告诉排序算法“每个false都比后面的false小”。这种情况下,TimSort的合并逻辑刚好没触发到契约校验的矛盾点,但这并不代表比较器是正确的——如果换一组测试数据(比如多个true和false混合),依然会抛出异常。
正确的比较器写法
要实现“true排在前面,false排在后面”的排序逻辑,正确的比较器应该覆盖所有相等的情况,比如:
// 方式1:明确处理相等情况 item.sort((a, b) -> { if (a.equals(b)) { return 0; } // true在前,所以true比false小(返回-1),false比true大(返回1) return a ? -1 : 1; }); // 方式2:利用Boolean自带的compare方法,更简洁 item.sort((a, b) -> Boolean.compare(b, a)); // 倒序,true在前
这样的比较器完全遵守契约,不会再抛出异常。
内容的提问来源于stack exchange,提问作者C. Koenig
相关产品推荐
相关产品推荐

