自定义比较器必须与排序逻辑严格一致,binarysearch不验证comparator一致性;排序和查找须用完全相同的comparator实例或语义等价lambda;compare方法需满足自反、反对称、传递性;避免耗时操作;null值需显式处理;负返回值按-insertionindex-1解码插入位置。

自定义比较器必须与排序逻辑严格一致
binarySearch 不会验证你用的 Comparator 是否和排序时用的是同一个。如果排序用了 Comparator.comparing(Person::getAge),查找时却用 Comparator.comparing(Person::getName),结果完全不可靠——哪怕数据看起来“碰巧对了”,也是偶然。
常见错误是:先调 Collections.sort(list, ageComp),后面却写 Collections.binarySearch(list, target)(无参版本)。这时它会尝试用 Comparable 自然序比较,而 Person 没实现 Comparable,或实现逻辑与 age 无关,直接导致 ClassCastException 或返回错误索引。
- 排序和查找必须使用**完全相同的 Comparator 实例或语义等价的 lambda**
- Comparator 的
compare(a,b)必须满足自反性、反对称性、传递性,否则二分过程可能陷入死循环或越界 - 避免在 Comparator 中做耗时操作(如远程调用、文件读取),它会在每次比较中被反复调用
处理 null 值要显式声明策略
默认情况下,大多数内置 Comparator(如 Comparator.nullsFirst())能安全处理 null;但自己写的 Comparator 如果没考虑 null,遇到 list 中有 null 元素时,binarySearch 运行中会抛 NullPointerException。
例如,按姓名查找 Person,若部分对象 name 为 null:
- 错:
(p1, p2) -> p1.getName().compareTo(p2.getName())—— 遇到 null 直接 NPE - 对:
Comparator.comparing(Person::getName, Comparator.nullsLast(String::compareTo)) - 或手动判空:
(p1, p2) -> { if (p1.getName() == null && p2.getName() == null) return 0; if (p1.getName() == null) return -1; if (p2.getName() == null) return 1; return p1.getName().compareTo(p2.getName()); }
插入点计算要直接解码,别手算
返回值为负数时,它不是“失败代码”,而是编码后的插入位置。公式统一为:插入索引 = -returnVal - 1。
比如返回 -5,插入位置就是 4;返回 -1,说明应插在开头(索引 0);返回 -(list.size() + 1),说明应插在末尾(索引 list.size())。
- 不要写成
Math.abs(returnVal) - 1,负数取绝对值再减 1 和-returnVal - 1等价,但后者更直观、不易出错 - 插入前记得检查该位置是否合法:
0 ,这个范围始终成立,无需额外校验 - 若需保持有序插入,可直接用
list.add(insertionPoint, target)
避开 LinkedList 和含基本类型数组的陷阱
binarySearch 内部依赖随机访问(get(i) 是 O(1))。在 LinkedList 上调用,每次定位中间元素都要从头遍历,整体退化为 O(n log n),比线性查找还慢。
数组方面:只能传包装类型数组(Integer[]、String[]),不能传 int[]、double[]。因为 Arrays.asList(int[]) 返回的是一个只含单个 int[] 元素的 List,不是你期望的数值列表。
- 正确做法:
Integer[] arr = {1, 3, 5}; List<integer> list = Arrays.asList(arr);</integer> - 基本类型数组请改用
Arrays.binarySearch(int[], key),它是专为此优化的重载 - 优先选用
ArrayList存储有序数据,兼顾性能与通用性











