☕ 第七章 集合
📄 一句话概括

Collections 管集合、Arrays 管数组——两个全 static 的官方工具箱,别人手写几十行的活它们一条命令搞定。这节课 = 6.3 System 工具类套路的二次复用 + 2.7 数组欠的"打印地址"旧账收网。

1. 先分清两个长得像的兄弟

⚠️ 千万别 new

两个工具类的构造方法都是私有的(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> 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. 常见误区

  1. Collections 和 Collection 混用——前者是工具类(复数),后者是接口(单数);new Collections() 编译报错。
  2. Arrays.toString(arr) 打印的是数组,Collections 没有 toString——想打印集合直接 System.out.println(list) 就行(ArrayList 自己重写了 toString,4.5 的规矩)。
  3. Arrays.sort(arr) 没有返回值——写 int[] b = Arrays.sort(arr) 编译报错,它原地修改。
  4. binarySearch 之前忘了 sort——结果错误且不报错,最阴的坑。
  5. 以为 Arrays.asList 返回的 List 和 ArrayList 一样能增删——定长,add/remove 就炸;要增删就再包一层 new ArrayList<>(...)。
  6. 以为 Collections.sort 能排任何类型——只排"签了比较合同"的类型,自定义类直接排会炸 ClassCastException。

7. 折叠自测题

❓ Q1:`Collection` 和 `Collections` 只差一个 s,分别是什么?

Collection 是接口(List/Set 的总合同);Collections 是工具类(全 static 方法)。工具类不能 new,接口不能 new——它俩都不能 new,但原因完全不同。

❓ Q2:`Arrays.binarySearch` 为什么必须先 sort?不 sort 会怎样?

二分查找靠"砍一半"定位:中间值比目标大就往左找,小就往右找——这个逻辑只有在有序数组上才成立。乱序数组"砍一半"砍错了方向,结果错误且不报错,是最危险的坑。

❓ Q3:`Arrays.asList("甲","乙")` 返回的东西能 set 吗?能 add 吗?

能 set(改内容,长度没变);不能 add/remove(长度变了,底层定长数组装不下)→ 抛 UnsupportedOperationException。想增删:new ArrayList<>(Arrays.asList(...))。

❓ Q4:把集合交给别的代码用,又怕它偷偷改,怎么办?

Collections.unmodifiableList(list)(Set/Map 有对应 unmodifiable 系列)包一层,任何修改操作抛 UnsupportedOperationException——运行时异常,编译器不拦,所以要在写代码时自己想清楚。

❓ Q5:Collections.shuffle 在现实中能干什么?写一个"随机点名器"的思路。

把全班名单装进 List<String> → Collections.shuffle(list) → 取 list.get(0) 就是被点到的。洗牌 = 公平随机,比 6.4 的 random 公式省事得多。

8. 下一步学什么

相关笔记