Collection = 全家族签的总合同——add/remove/contains 这些方法 List 和 Set 都得会,所以学一次全家族通用。4.3 的接口思想、4.5 的 equals 规矩在这全面回收。
7.2 Collection接口
1. Collection 是接口,不是类
7.1 用了 ArrayList,但家族地图里站在最顶上的 Collection 是个接口(interface)——正是 4.3 学的那种"合同":
- Collection 只定规矩:「家族成员必须会 add、remove、contains、size……」
- 实现类负责干活:ArrayList 用数组实现,HashSet 用哈希表实现,各有各的干法
Collection(接口:总合同)
↑ implements
┌──┴──────────────┐
List(子合同) Set(子合同)
↑ ↑
ArrayList 等 HashSet 等
2. 多态声明:左边写合同,右边换实现
Collection<String> c = new ArrayList<>(); // ✓ 多态写法
这正是 4.4「编译看左、运行看右」的实战主场:
- 左边写
Collection,只能调合同里的方法(够用了) - 哪天想换实现,只改右边一个词,左边和业务代码不动
能写 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. 常见误区
编译报错——接口不能实例化(4.3 的规矩)。只能 new 实现类:new ArrayList<>()。
底层调 equals。String/Integer 官方重写好了直接用;自己的类不重写 equals,contains 就是摆设(永远按地址比)。
Collection 接口的 remove 只有按内容版;remove(0) 要在 ArrayList 上才是按下标。类型写 Collection 时按下标删编译就报错——接口合同里没这条。
抛 ConcurrentModificationException(运行时异常)。遍历期间结构变了,迭代器抽屉对不上号。要删用迭代器的 remove。
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. 自测一下
接口。接口只定方法签名没有实现,没有构造方法——不能实例化(4.3 规矩),只能 new 实现类如 ArrayList。
左边接口右边实现类(编译看左运行看右,4.4)。好处:代码不绑死实现,换实现类只改右边一个词。
挨个调元素的 equals 比内容。自己的类必须重写 equals(否则用 Object 默认版比地址,永远 false)——4.5 规矩第一次真踩痛处。
增强 for 底层就是迭代器(自动挡 vs 手动挡)。增强 for 不能遍历中增删、拿不到下标;迭代器可以用 it.remove() 安全删除。
Person 没重写 equals → contains 调的是 Object 默认版 → 默认版比地址(==)→ 两个 new 地址不同 → false。解法:Person 重写 equals(顺带按 4.5 搭伴规矩重写 hashCode,为 HashSet 去重做准备)。
9. 下一步学什么
- ☑ 7.3 List接口 ✅ —— 已完成:List 三大特点(有序/带下标/可重复)、四个下标专属方法、三种遍历、ArrayList vs LinkedList、并发修改异常正式收网。点此复习
相关笔记
- 第七章 集合/7.1 集合概述 ← 上一篇:家族地图,本节开始细读总纲
- 第四章 面向对象(下)/4.3 抽象类和接口:接口=合同的思想回收
- 第四章 面向对象(下)/4.5 Object类:equals 默认版比地址——contains 踩痛的根源
- 第四章 面向对象(下)/4.4 多态:编译看左运行看右的实战主场