
Collections.shuffle() 就行,别自己写随机交换Java 标准库已经提供了经过充分测试、符合 Fisher-Yates 算法的实现,Collections.shuffle() 是唯一推荐方式。自己手写 for 循环 + Random.nextInt() 交换不仅容易出错(比如边界错位导致分布不均),还可能引入 bias(偏向性),尤其在小集合上明显。
它只接受 List 类型,对 Set 或 Map 不能直接调用——这是最常被忽略的前提条件。
List 实现(如 ArrayList、LinkedList),不可变列表(如 Arrays.asList() 返回的)会抛 UnsupportedOperationException
Random 实例,线程不安全;高并发场景下建议传入线程安全的 ThreadLocalRandom.current()
Collections.shuffle() 的两个重载版本怎么选它有两个公开方法:
public static void shuffle(List<?> list) public static void shuffle(List<?> list, Random rnd)
区别在于是否指定 Random 实例:
立即学习“Java免费学习笔记(深入)”;
Random(),种子来自系统时间 —— 适合单次、非敏感场景(如 UI 列表刷新)Random,或传 ThreadLocalRandom.current() 避免多线程竞争;也可传固定种子用于可重现的测试(例如 new Random(42))SecureRandom 虽然更安全,但性能差,一般业务无需使用没有直接支持。必须先转成 List,再 shuffle,最后按需重建。但要注意语义丢失风险:
HashSet → new ArrayList(set) → shuffle() → 若需保持唯一性,结果仍是合法集合;但原始插入顺序本就无意义,所以没问题LinkedHashMap(有序)→ 洗牌后若要还原为 map,只能用 new LinkedHashMap(list.size()) 并逐个 put,此时插入顺序即为洗牌顺序,原访问顺序已丢弃TreeSet 不建议洗牌:它依赖 compareTo() 维持结构,强行转 list shuffle 后再塞回去,会立刻重新排序,等于白干示例(安全转换):
Listtemp = new ArrayList<>(myHashSet); Collections.shuffle(temp, ThreadLocalRandom.current()); // 后续用 temp,或重建 set:new HashSet<>(temp)
这些不是异常,但会导致行为不符合预期:
UnsupportedOperationException:对 Arrays.asList(arr) 返回的 list 调用 shuffle —— 它是固定大小的,不支持 add/remove,而 shuffle 内部可能触发结构修改检查(取决于 JDK 版本)。解决:包装一层 new ArrayList(Arrays.asList(...))
Random,每次运行不同。单元测试务必用 new Random(123) 控制可重现性list.stream().map(...).collect(...) 后 shuffle 原 list —— 注意 stream 是惰性的,collect 才生成新 list,原 list 不受影响,shuffle 的可能是旧引用真正需要小心的,是类型判断和可变性检查 —— 这些不会编译报错,但运行时才暴露。