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

Collection = 全家族签的总合同——add/remove/contains 这些方法 List 和 Set 都得会,所以学一次全家族通用。4.3 的接口思想、4.5 的 equals 规矩在这全面回收。

7.2 Collection接口

1. Collection 是接口,不是类

7.1 用了 ArrayList,但家族地图里站在最顶上的 Collection 是个接口(interface)——正是 4.3 学的那种"合同":

Collection(接口:总合同)
   ↑ implements
┌──┴──────────────┐
List(子合同)      Set(子合同)
 ↑                  ↑
ArrayList 等        HashSet 等

2. 多态声明:左边写合同,右边换实现

Collection<String> c = new ArrayList<>();   // ✓ 多态写法

这正是 4.4「编译看左、运行看右」的实战主场:

💡 为什么不全写成 ArrayList?

能写 ArrayList<String> list = new ArrayList<>();,也没错。但声明成接口,代码就不绑死在某个实现上——面向接口编程是企业开发的基本姿势。本章教学先用具体类型,看到 Collection c = new ... 你要认识。

3. 六大常用方法(全家族通用)

Collection<String> c = new ArrayList<>();
c.add("波仔");                          // 添加,返回 boolean
c.add("墨池");
System.out.println(c);                  // [波仔, 墨池] —— toString 已重写
System.out.println(c.contains("墨池")); // true
System.out.println(c.size());           // 2
System.out.println(c.isEmpty());        // false
c.remove("波仔");                       // 按内容删
c.clear();                              // 清空:集合还在,东西没了
Object[] arr = c.toArray();             // 转数组:集合↔数组的桥
方法 作用 备注
add(e) 添加元素 List 永远 true;Set 重复时返回 false
remove(e) 按内容删 底层走 equals 找人
contains(e) 查在不在 底层也走 equals,见下节
size() / isEmpty() 个数 / 判空 方法带括号,别写成 length
clear() 清空 删的是元素,不是集合对象
toArray() 转数组 和 Arrays.asList()(数组转集合)是一对桥

4. contains 的底层秘密:equals 规矩回收

contains("墨池") 是怎么找到人的?从第一个元素开始挨个调 equals 比——比的是内容,不是地址。

这就分两种情况:

Collection<String> names = new ArrayList<>();
names.add(new String("波仔"));
System.out.println(names.contains("波仔"));   // true —— String 重写过 equals,按内容比

Collection<Person> people = new ArrayList<>();
people.add(new Person("波仔", 25));
System.out.println(people.contains(new Person("波仔", 25)));  // false!

第二个为什么 false?Person 是自己的类,没重写 equals——用的是 Object 默认版(比地址,4.5 学的),两个 new Person("波仔",25) 地址不同。

💡 结论

想让 contains / remove 按内容找到自己的对象,必须重写 equals(IDEA 里 alt+insert 一键生成)。这是 4.5 那句"自定义类要重写 equals 才能比内容"第一次真正踩到痛处。

5. 两种遍历方式

姿势一:增强 for(for-each)—— 首选

for (String s : c) {           // 冒号读作"遍历 c 中的每个"
    System.out.println(s);
}

写法短、不用管下标。限制:遍历中不能 add/remove(会抛 ConcurrentModificationException),也拿不到当前下标。

姿势二:迭代器 Iterator

import java.util.Iterator;

Iterator<String> it = c.iterator();   // 集合自己生产迭代器
while (it.hasNext()) {                // 问:还有下一个吗?
    String s = it.next();             // 拿一个(指针后移)
    System.out.println(s);
}

迭代器三步舞:iterator() 领取 → hasNext() 探路 → next() 取货。增强 for 底层其实就是迭代器——它俩是同一个东西的手动挡和自动挡。

💡 遍历中要删元素怎么办?

用迭代器自己的 it.remove()(它知道内部结构,删了不炸);用集合的 c.remove() 边遍历边删会抛并发修改异常——细节留到遍历节展开。

6. 常见误区

⚠️ 误区 1:`new Collection<>()` 想直接创建

编译报错——接口不能实例化(4.3 的规矩)。只能 new 实现类:new ArrayList<>()。

⚠️ 误区 2:以为 contains 是拿 `==` 比

底层调 equals。String/Integer 官方重写好了直接用;自己的类不重写 equals,contains 就是摆设(永远按地址比)。

⚠️ 误区 3:`c.remove(0)` 删"值为 0 的元素"

Collection 接口的 remove 只有按内容版;remove(0) 要在 ArrayList 上才是按下标。类型写 Collection 时按下标删编译就报错——接口合同里没这条。

⚠️ 误区 4:增强 for 里 add/remove 元素

抛 ConcurrentModificationException(运行时异常)。遍历期间结构变了,迭代器抽屉对不上号。要删用迭代器的 remove。

⚠️ 误区 5:clear 之后以为集合对象没了

clear 清的是内容,c 这个引用和集合对象还在,c.add(...) 照样能用。想整个扔掉就让引用置 null。

7. 动手试一试(30 秒实验)

新建 Demo02(import java.util.*;):

实验 1:Collection<String> c = new ArrayList<>(); add 三个后直接打印——再看 new Collection<>(),亲眼看"接口不能实例化"报错。

实验 2:自己建 Person 类(不重写 equals),add 一个再 contains 一个同内容的——看 false;alt+insert 生成 equals 后再试——true。equals 重写前后的差距一眼见分晓。

实验 3:增强 for 遍历中调 c.remove(...)——看 ConcurrentModificationException 爆出。

实验 4:迭代器三步舞走一遍,再试 it.remove() 边遍历边删——对比实验 3,体会"删元素找迭代器"。

8. 自测一下

❓ Q1:Collection 是类还是接口?为什么不能 `new Collection<>()`?

接口。接口只定方法签名没有实现,没有构造方法——不能实例化(4.3 规矩),只能 new 实现类如 ArrayList。

❓ Q2:`Collection c = new ArrayList<>();` 这行的多态体现?好处是什么?

左边接口右边实现类(编译看左运行看右,4.4)。好处:代码不绑死实现,换实现类只改右边一个词。

❓ Q3:contains 找人的底层机制?自己的类要注意什么?

挨个调元素的 equals 比内容。自己的类必须重写 equals(否则用 Object 默认版比地址,永远 false)——4.5 规矩第一次真踩痛处。

❓ Q4:增强 for 和迭代器什么关系?各自的限制?

增强 for 底层就是迭代器(自动挡 vs 手动挡)。增强 for 不能遍历中增删、拿不到下标;迭代器可以用 it.remove() 安全删除。

❓ Q5(毕业题):往 Collection<Person> 里 add 了 new Person("波仔",25),contains(new Person("波仔",25)) 返回 false 的完整原因链?

Person 没重写 equals → contains 调的是 Object 默认版 → 默认版比地址(==)→ 两个 new 地址不同 → false。解法:Person 重写 equals(顺带按 4.5 搭伴规矩重写 hashCode,为 HashSet 去重做准备)。

9. 下一步学什么


相关笔记