使用永不返回0的自定义Comparator的TreeSet存在哪些副作用?
不返回0的Comparator会引发哪些问题?
常规的Comparator实现
先看一个符合规范(虽不算高效)的Comparator写法,它能正确区分元素的小于、大于、等于三种关系:
private final class SampleComparator implements Comparator<Integer> { @Override public int compare(Integer a, Integer b) { if (a < b) return -1; if (a > b) return 1; return 0; } };
永远不返回0的错误实现
下面这个实现存在问题——它永远不会返回0,因为当两个元素相等时,会走a <= b的分支返回-1:
private final class SampleComparator implements Comparator<Integer> { @Override public int compare(Integer a, Integer b) { if (a <= b) return -1; // 小于等于 if (a > b) return 1; return 0; } };
这种错误实现的副作用
- 违反Comparator核心契约:Java规定Comparator必须满足自反性——
compare(a,a)必须返回0,但这个实现里compare(a,a)返回-1,直接打破规则,所有依赖该Comparator的集合都会出现不可预测的行为。 - TreeSet彻底失效:TreeSet靠Comparator返回0判断元素重复,用这个实现后,所有元素都会被判定为“小于等于”其他元素,看似能无限添加重复元素,但实际上集合的排序逻辑会完全混乱,遍历顺序不符合预期,甚至用
contains查找元素时,明明存在的元素也会返回false。 - TreeMap键唯一性被破坏:如果用在TreeMap里,相同的键会被当成不同键存入,Map中出现重复键,后续取值、遍历都会出现异常。
- 集合性能崩盘:TreeSet/TreeMap底层是红黑树,错误的比较逻辑会导致树结构失衡,插入、查找、删除的性能大幅下降,极端情况下甚至可能触发死循环或者抛出异常。
正确实现“有序存储重复元素”的方案
如果需要在有序结构里存储重复元素,别打Comparator的主意,正确做法是使用TreeMap<K, List<V>>:
- 用TreeMap的键维护排序逻辑,每个键对应一个List来存储所有相同的元素。
- 插入元素时先检查键是否存在:存在就把元素加入对应List;不存在就新建List存入元素,再把键值对放进TreeMap。
- 这样既保留了有序性,又能合法存储重复元素,完全符合集合规范。
内容的提问来源于stack exchange,提问作者user1722025
相关产品推荐
相关产品推荐

