Collections 管集合、Arrays 管数组——两个全 static 的官方工具箱,别人手写几十行的活它们一条命令搞定。这节课 = 6.3 System 工具类套路的二次复用 + 2.7 数组欠的"打印地址"旧账收网。
1. 先分清两个长得像的兄弟
Collection(7.2):接口,集合家族的总合同——单数,是个标准。Collections:工具类,全 static 方法的服务员——复数,是个帮手。
两个工具类的构造方法都是私有的(Collections 干脆没有构造入口),全程 Collections.sort(...)、Arrays.toString(...) 类名直接调。写 new Collections() 直接编译报错——6.3 System 类讲过的工具类设计套路,这里是官方成品第二例。
2. Collections 工具箱六件套
| 方法 | 干什么 | 备注 |
|---|---|---|
Collections.sort(list) |
排序(从小到大) | String/Integer 官方已内置比较规矩,直接用 |
Collections.shuffle(list) |
洗牌(打乱顺序) | 发牌、抽奖、乱序出题全靠它 |
Collections.reverse(list) |
反转(头尾对调) | 倒序一条命令 |
Collections.addAll(list, e1, e2, ...) |
批量添加 | 一行顶五个 add |
Collections.max(list) / min(list) |
找最大 / 最小 | 省掉手写遍历找极值 |
Collections.swap(list, i, j) / fill(list, e) |
交换 / 填充 | 换位置 / 全部改成同一个值 |
sort 为什么能排 String 和 Integer?
因为 String、Integer 内部已经"写好了比较规矩"(实现了 Comparable 接口——4.3 讲接口思想时说过的"合同")。所以:
- 排
List<String>、List<Integer>→ 直接Collections.sort(list),好使。 - 排
List<Person>自定义类 → 运行时抛 ClassCastException(4.4 集齐的三大新手异常之一:转不了/比不了)。想排自己的类,得先让类"签合同"(实现 Comparable)——那是后面"泛型与排序进阶"的活,本节先会认这个报错。
隐形套装:不可变包装
List<String> locked = Collections.unmodifiableList(list);
locked.add("新货"); // 运行时炸:UnsupportedOperationException
包装出来的集合只读——别人想往里 add/remove 就抛运行时异常(5.2 收容所那派,和 6.7 的 DateTimeFormatter 家族脾气相通:不可变 = 安全,代价是不能改)。什么时候用?把一份名单交给别的代码时,包一层防止它偷偷改你的数据。
3. Arrays 工具箱——2.7 数组的官方外挂
| 方法 | 干什么 | 备注 |
|---|---|---|
Arrays.toString(arr) |
打印成 [a, b, c] |
收 2.7 的旧账:直接 println(arr) 打出来是 [I@1b6d3586(地址),一直不知道怎么好好打印 |
Arrays.sort(arr) |
原地排序 | 从小到大,无返回值,改的是原数组 |
Arrays.binarySearch(arr, key) |
二分查找,返回下标 | ⚠ 前提:数组必须先 sort 过,乱序数组查出来的结果是错的(还不报错!) |
Arrays.copyOf(arr, newLen) |
复制出指定长度的新数组 | 2.7 讲过的"数组扩容"原理,官方就是拿它实现的 |
Arrays.asList(a, b, c) |
把几个元素变成 List | ⚠ 见下面大坑 |
本节第一大坑:Arrays.asList 是"定长"的
List<String> list = Arrays.asList("甲", "乙", "丙");
list.set(0, "丁"); // ✓ 好使(能改内容)
list.add("戊"); // ✗ 运行时炸:UnsupportedOperationException
asList 返回的 List 看着是 List,其实底层数组定死长度——增删一律炸。想要能增删的版本,再包一层:
List<String> list = new ArrayList<>(Arrays.asList("甲", "乙", "丙"));
Collections 管集合,Arrays 管数组。List 集合底层就是数组,所以它是这两个工具箱之间的"换乘站"——Arrays.asList 进站,list.toArray() 出站。
4. 动手时间(Demo07)
import java.util.*;
public class Demo07 {
public static void main(String[] args) {
// ===== Collections =====
List<Integer> scores = new ArrayList<>();
Collections.addAll(scores, 88, 59, 100, 72, 95); // 批量添加
System.out.println("原始: " + scores);
Collections.sort(scores); // 排序
System.out.println("排序: " + scores);
System.out.println("最高分: " + Collections.max(scores)
+ ",最低分: " + Collections.min(scores));
Collections.reverse(scores); // 反转
System.out.println("反转: " + scores);
Collections.shuffle(scores); // 洗牌
System.out.println("洗牌: " + scores);
// ===== Arrays =====
int[] arr = {35, 12, 99, 47};
System.out.println("数组直接打印: " + arr); // 地址 [I@...
System.out.println("toString: " + Arrays.toString(arr));
Arrays.sort(arr); // 先排序
System.out.println("排序后: " + Arrays.toString(arr));
System.out.println("找 47: 下标 " + Arrays.binarySearch(arr, 47));
// ===== asList 大坑 =====
List<String> fixed = Arrays.asList("甲", "乙", "丙");
List<String> flex = new ArrayList<>(Arrays.asList("甲", "乙", "丙"));
flex.add("丁"); // 包一层之后才能加
System.out.println(flex);
// ===== 不可变包装 =====
List<String> locked = Collections.unmodifiableList(flex);
// locked.add("敢改就炸"); // UnsupportedOperationException
System.out.println("锁死名单: " + locked);
}
}
5. 30 秒实验
实验 1:System.out.println(arr) 直接打 int 数组——看到 [I@xxxx,再换 Arrays.toString(arr)——看到 [12, 35, 47, 99]。2.7 欠的"打印地址"旧账当场收网。
实验 2:Arrays.sort(arr) 之后故意不排序直接 binarySearch(arr, 99)——多跑几次看下标,结果时对时错且不报错。二分查找的前提违反了不吭声,这比抛异常更危险。
实验 3:Arrays.asList("甲","乙").add("丙")——运行时炸 UnsupportedOperationException;换成 new ArrayList<>(Arrays.asList(...)) 再 add——好使。
实验 4:Collections.unmodifiableList(list) 包装后 add——同样炸 UnsupportedOperationException。两个工具箱共用同一件"不可变"铠甲。
实验 5:新建一个 Person 类的 ArrayList,直接 Collections.sort(...)——炸 ClassCastException。自己的类没签"比较合同",排不了。
6. 常见误区
Collections和Collection混用——前者是工具类(复数),后者是接口(单数);new Collections()编译报错。Arrays.toString(arr)打印的是数组,Collections没有 toString——想打印集合直接System.out.println(list)就行(ArrayList 自己重写了 toString,4.5 的规矩)。Arrays.sort(arr)没有返回值——写int[] b = Arrays.sort(arr)编译报错,它原地修改。- binarySearch 之前忘了 sort——结果错误且不报错,最阴的坑。
- 以为
Arrays.asList返回的 List 和 ArrayList 一样能增删——定长,add/remove 就炸;要增删就再包一层new ArrayList<>(...)。 - 以为 Collections.sort 能排任何类型——只排"签了比较合同"的类型,自定义类直接排会炸 ClassCastException。
7. 折叠自测题
Collection 是接口(List/Set 的总合同);Collections 是工具类(全 static 方法)。工具类不能 new,接口不能 new——它俩都不能 new,但原因完全不同。
二分查找靠"砍一半"定位:中间值比目标大就往左找,小就往右找——这个逻辑只有在有序数组上才成立。乱序数组"砍一半"砍错了方向,结果错误且不报错,是最危险的坑。
能 set(改内容,长度没变);不能 add/remove(长度变了,底层定长数组装不下)→ 抛 UnsupportedOperationException。想增删:new ArrayList<>(Arrays.asList(...))。
Collections.unmodifiableList(list)(Set/Map 有对应 unmodifiable 系列)包一层,任何修改操作抛 UnsupportedOperationException——运行时异常,编译器不拦,所以要在写代码时自己想清楚。
把全班名单装进 List<String> → Collections.shuffle(list) → 取 list.get(0) 就是被点到的。洗牌 = 公平随机,比 6.4 的 random 公式省事得多。
8. 下一步学什么
- ☑ 7.8 Stream(流)✅ —— 点此复习:JDK8 数据流水线——中间操作链 + 终结操作,4.7 的 Lambda 正式转正,一行流顶旧写法一个循环。
相关笔记
- 第七章 集合/7.1 集合概述:集合家族地图与"只能装对象"铁规矩
- 第七章 集合/7.4 集合遍历:排好序之后怎么优雅地遍历
- 第六章 Java API/6.3 System类:工具类设计套路(构造私有 + 全 static)的第一次出场
- 2.7 数组:Arrays 外挂的本体——直接 println 打地址的旧账
- 第四章 面向对象(下)/4.3 抽象类和接口:sort 能排 String/Integer 的原因——内置的"比较合同"