Android按文件名排序报IllegalArgumentException比较方法违反通用约定问题咨询
问题原因
你遇到的IllegalArgumentException: Comparison method violates its general contract!异常是JDK的TimSort排序算法抛出的,当自定义Comparator违反了以下三个核心约定时就会触发:
- 对称性:
compare(a,b) == -compare(b,a)必须始终成立 - 自反性:
compare(a,a) == 0必须始终成立 - 传递性:如果
compare(a,b)>0且compare(b,c)>0,则compare(a,c)>0必须成立
你写的FileNameComparator存在多个违反约定的逻辑缺陷:
缺陷1:前导0处理逻辑完全错误
进入数字区域的前导0跳过逻辑中,循环判断用的是进入分支前缓存的cLeft/cRight值,i/j递增后没有重新获取当前位置的字符:
// 错误代码 while (i < lenLeft && cLeft == '0') i++ while (j < lenRight && cRight == '0') j++
比如字符串是"0012",第一次取的cLeft是'0',i递增后判断条件还是用原来的cLeft == '0',会直接把i加到字符串长度越界,完全没有实现跳过前导0的效果。
缺陷2:循环结束后的返回逻辑错误
遍历到任意字符串结束后,你直接返回两个字符串当前最后一个位置的字符差值:
// 错误代码 if (i == lenLeft) i-- if (j == lenRight) j-- return csLeft[i] - csRight[j]
这会导致大量完全不相等的字符串被判定为相等,比如compare("a", "aa")会返回0,直接违反对称性约定;对于长度不同的数字字符串也会得到完全错误的结果,比如compare("12", "123")的返回值完全由最后一个字符决定,而不是数字的实际大小。
缺陷3:数字长度判断逻辑缺失
当两个数字字符串长度不同时,你没有优先判定长度更长的数字更大,比如"123"和"45",你的逻辑刚好得到了正确结果,但如果是"123"和"12",就会得到错误的比较结果。
可复现异常的测试用例
以下代码可以100%触发你遇到的异常:
fun testComparatorCrash() { val comparator = FileNameComparator() val testList = listOf("1", "01", "001", "2", "02", "a", "aa", "b", "bb") Collections.sort(testList, comparator) }
单独调用比较方法也可以直接验证约定违反:
// 输出0,两个完全不同的字符串被判定为相等,违反约定 println(FileNameComparator().compare("a", "aa"))
修复建议
- 修复前导0处理逻辑,每次i/j递增后重新读取当前位置的字符判断是否是前导0
- 循环结束后的返回逻辑优先判断长度:如果仍在数字区域,长度更短的数字更小;如果不在数字区域,长度更短的字符串更小
- 数字比较时优先比较有效数字的长度,长度更长的数字更大,再逐位比较相同位置的差值
- 所有逻辑分支都要保证
compare(a,b) == -compare(b,a)成立
内容的提问来源于stack exchange,提问作者Shouheng Wang
相关产品推荐
相关产品推荐

