Java集合的排序与比较器语法

Arrays.sort()适用于原生数组,Collections.sort()仅适用于List;Comparator有匿名类、方法引用、lambda三种写法,lambda最常用;sort时Comparator优先级高于Comparable;自定义Comparator需保证一致性、无副作用、线程安全。

Arrays.sort() 和 Collections.sort() 的适用场景区别

数组和集合的排序不能混用:对 int[]Collections.sort() 会编译失败,因为后者只接受 List;而 Arrays.sort()ArrayList 直接传参会报错,必须先转成数组或用其重载版本。

  • Arrays.sort() 适用于原生数组(int[]String[]MyObj[]),也支持传入 Comparator
  • Collections.sort() 只接受 List 实现(如 ArrayListLinkedList),底层调用的是 Arrays.sort()Object[] 版本
  • 对基本类型数组(如 int[])排序,只能用 Arrays.sort();它不接受 Comparator,因为基本类型无法参与泛型比较逻辑

Comparator 接口的三种写法及何时必须用 lambda

从 Java 8 起,Comparator 是函数式接口,但不是所有写法都等价。匿名内部类、静态方法引用、lambda 表达式在可读性和性能上差异明显。

  • 匿名类适合复杂多字段逻辑,但冗长:
    new Comparator() {
        public int compare(Person a, Person b) {
            return Integer.compare(a.getAge(), b.getAge());
        }
    }
  • 方法引用仅限单方法调用,比如 String::compareTo,不能组合条件
  • lambda 最常用:(a, b) -> Integer.compare(a.getAge(), b.getAge());注意返回值必须是 int,不能写成 return a.getAge() > b.getAge() 这种布尔表达式
  • 如果字段可能为 null,别直接链式调用 a.getName().compareTo(b.getName()),应改用 Comparator.nullsLast(Comparator.comparing(Person::getName))

Comparable 和 Comparator 混用时的优先级陷阱

当一个类同时实现了 Comparable(定义自然顺序)又传入了 Comparator(定制顺序),sort() 方法永远以 Comparator 为准,Comparable 完全被忽略。

  • TreeSet 默认用 Comparable,但如果构造时传入 Comparator,则全部按该规则去重和排序
  • Arrays.sort(personArray)(无 comparator)会尝试调用 person.compareTo(),若未实现 Comparable 则抛 ClassCastException
  • 不要在 compareTo() 里调用 Comparator,这会造成循环依赖或逻辑混乱
  • 若业务中存在多种排序需求(按年龄、按姓名、按创建时间),应统一提供多个静态 Comparator 实例,而非让类实现 Comparable

自定义 Comparator 中的稳定性与副作用风险

Java 的排序算法(如 Timsort)是稳定的,但前提是 compare() 方法满足“一致性”:对同一对对象多次调用必须返回相同结果;否则可能触发 IllegalArgumentException: Comparison metho

d violates its general contract!

  • 避免在 compare() 中修改对象状态(如调用 setSortIndex()
  • 禁止使用非确定性值,例如 new Date().getTime()Math.random()
  • 字段类型不一致容易翻车:比如用 Double.compare(a.getScore(), b.getScore()) 处理 null,但 double 基本类型无法为 null,应确保字段是 Double 包装类
  • 复合排序记得用 thenComparing() 链式调用,而不是手动写嵌套 if-else,否则易漏 return 0 导致逻辑错误
实际项目中最容易被忽略的是:Comparator 实例是否线程安全。它本身无状态就是安全的,但若内部捕获了外部可变变量(如某个 Map weightMap),而该 map 在排序过程中被其他线程修改,结果就不可预测。