Collection 接口与通用 API | JavaSE
Collection 接口与通用 API
一、学习目标
完成本章后,你应该能够:
- 能够解释
Collection<E>接口在 Java 集合体系中的定位。 - 能够使用
add、remove、contains、size、isEmpty、clear等通用 API。 - 能够使用
addAll、containsAll、removeAll、retainAll等批量集合操作。 - 能够使用
toArray完成集合到数组的转换。 - 能够理解
contains、remove(Object)与equals语义之间的关系。 - 能够理解 Collection 接口定义的是统一契约,而具体实现类可能限制某些修改操作。
二、核心知识
2.1 Collection 是什么
Collection<E> 是 Java 集合层次结构中的核心根接口。
它表示:
由一组元素构成的对象。
例如:
Java
MySQL
Redis
Git
这些元素可以放入:
Collection<String>
中。
Collection 自己是接口,因此通常不会直接:
new Collection<>();
而是通过具体实现类创建对象。
例如:
Collection<String> names = new ArrayList<>();
这里:
Collection<String>
负责描述:
这是一组 String 元素
而:
ArrayList
负责真正保存这些元素。
2.2 为什么先学习 Collection API
后面会学习:
List
Set
Queue
它们都处于 Collection 集合体系之中。
因此 Collection 定义的一批通用能力,可以迁移到大量具体集合。
例如:
add()
remove()
contains()
size()
isEmpty()
clear()
以后使用:
ArrayList
LinkedList
HashSet
LinkedHashSet
TreeSet
时都会不断遇到这些能力。
因此学习顺序应该是:
Collection 通用能力
↓
List / Set 等接口特性
↓
具体集合实现
而不是每学习一个集合就重新背一遍同样的方法。
2.3 Collection 常用 API 总览
常用 API 可以分成几类。
添加元素
boolean add(E e)
删除元素
boolean remove(Object o)
void clear()
查询集合状态
boolean contains(Object o)
boolean isEmpty()
int size()
批量操作
boolean addAll(Collection<? extends E> c)
boolean containsAll(Collection<?> c)
boolean removeAll(Collection<?> c)
boolean retainAll(Collection<?> c)
条件删除
boolean removeIf(Predicate<? super E> filter)
转换数组
Object[] toArray()
<T> T[] toArray(T[] array)
<T> T[] toArray(IntFunction<T[]> generator)
遍历与 Stream
Collection 还提供:
iterator()
stream()
parallelStream()
但是这些会分别在:
05-03 Iterator、增强 for 与 forEach 遍历
GROUP 06 Stream
中展开。
本章不提前详细讲解。
三、使用方法
3.1 创建 Collection
首先导包:
import java.util.ArrayList;
import java.util.Collection;
创建集合:
Collection<String> technologies = new ArrayList<>();
完整形式:
Collection<String> technologies =
new ArrayList<String>();
因为 Java 支持泛型类型推断,右侧通常简写为:
new ArrayList<>();
3.2 add:添加元素
方法:
boolean add(E e)
作用:
向集合中添加一个元素。
例如:
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("MySQL");
technologies.add("Redis");
add 返回:
boolean
表示这次调用是否使集合发生了变化。
例如:
boolean result = technologies.add("Java");
System.out.println(result);
对于 ArrayList:
允许重复
因此正常添加通常返回:
true
但对于某些不允许重复元素的集合:
Set
如果添加重复数据,就可能返回:
false
具体行为需要结合实现类型分析。
3.3 size:获取元素数量
方法:
int size()
作用:
返回集合中元素的数量。
例如:
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("MySQL");
technologies.add("Redis");
System.out.println(technologies.size());
此时:
size = 3
注意:
size()
表示:
元素数量。
它不是数组中的:
length
也不是字符串中的:
length()
需要区分:
数组 → array.length
String → string.length()
Collection → collection.size()
3.4 isEmpty:判断集合是否为空
方法:
boolean isEmpty()
作用:
判断集合中是否没有元素。
例如:
Collection<String> technologies = new ArrayList<>();
System.out.println(technologies.isEmpty());
technologies.add("Java");
System.out.println(technologies.isEmpty());
在业务代码中,相比:
technologies.size() == 0
通常:
technologies.isEmpty()
表达意图更加直接。
3.5 contains:判断元素是否存在
方法:
boolean contains(Object o)
作用:
判断集合中是否存在指定元素。
例如:
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("MySQL");
System.out.println(
technologies.contains("Java")
);
System.out.println(
technologies.contains("Redis")
);
3.6 remove:删除指定元素
方法:
boolean remove(Object o)
作用:
删除集合中与指定对象匹配的一个元素。
例如:
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("MySQL");
technologies.add("Redis");
boolean result = technologies.remove("MySQL");
System.out.println(result);
System.out.println(technologies);
返回值表示:
是否成功找到并删除对应元素
如果集合中不存在:
technologies.remove("Python");
通常返回:
false
3.7 clear:清空集合
方法:
void clear()
作用:
删除集合中的全部元素。
例如:
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("MySQL");
technologies.add("Redis");
technologies.clear();
System.out.println(technologies);
System.out.println(technologies.isEmpty());
需要注意:
clear()
并不是:
把 technologies 变量变成 null
而是:
集合对象仍然存在
只是里面没有元素了
因此下面两个状态完全不同:
technologies.clear();
和:
technologies = null;
第一种:
变量仍然引用一个集合对象
集合大小为 0
第二种:
变量不再引用原集合对象
3.8 addAll:一次添加一批元素
方法:
boolean addAll(Collection<? extends E> c)
作用:
把另一个集合中的元素加入当前集合。
例如:
Collection<String> backend = new ArrayList<>();
backend.add("Java");
backend.add("Spring");
Collection<String> database = new ArrayList<>();
database.add("MySQL");
database.add("Redis");
backend.addAll(database);
System.out.println(backend);
结果从逻辑上可以理解为:
Java
Spring
MySQL
Redis
3.9 containsAll:是否包含另一集合中的全部元素
方法:
boolean containsAll(Collection<?> c)
例如:
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("Spring");
technologies.add("MySQL");
Collection<String> required = new ArrayList<>();
required.add("Java");
required.add("MySQL");
boolean result = technologies.containsAll(required);
System.out.println(result);
它判断的是:
required中的每个元素是否都能在technologies中找到。
3.10 removeAll:批量删除
方法:
boolean removeAll(Collection<?> c)
作用:
删除当前集合中所有同时存在于参数集合中的元素。
例如:
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("Spring");
technologies.add("MySQL");
technologies.add("Redis");
Collection<String> removed = new ArrayList<>();
removed.add("MySQL");
removed.add("Redis");
technologies.removeAll(removed);
System.out.println(technologies);
从集合运算角度,可以粗略理解为:
A.removeAll(B)
≈ A - B
3.11 retainAll:只保留共同元素
方法:
boolean retainAll(Collection<?> c)
作用:
当前集合只保留同时存在于参数集合中的元素。
例如:
Collection<String> studentA = new ArrayList<>();
studentA.add("Java");
studentA.add("MySQL");
studentA.add("Redis");
Collection<String> studentB = new ArrayList<>();
studentB.add("Java");
studentB.add("Python");
studentB.add("MySQL");
studentA.retainAll(studentB);
System.out.println(studentA);
此时 studentA 只保留两者共同拥有的内容。
可以近似理解为:
交集
即:
A ∩ B
3.12 removeIf:按照条件删除
Collection 还提供:
boolean removeIf(Predicate<? super E> filter)
例如:
Collection<Integer> scores = new ArrayList<>();
scores.add(95);
scores.add(42);
scores.add(78);
scores.add(30);
scores.removeIf(score -> score < 60);
System.out.println(scores);
作用是:
删除所有满足条件的元素。
这里出现了:
score -> score < 60
这是 Lambda 表达式。
由于后续有专门的 Lambda 章节,本章只需要能够理解:
score < 60 的元素会被删除
即可。
3.13 toArray:集合转换成数组
有时候某些旧 API 或其他程序只接受数组。
这时就需要:
Collection → Array
方式一:得到 Object[]
Collection<String> technologies = new ArrayList<>();
technologies.add("Java");
technologies.add("MySQL");
Object[] array = technologies.toArray();
得到:
Object[]
方式二:指定数组类型
String[] array =
technologies.toArray(new String[0]);
得到:
String[]
方式三:使用数组构造器引用
现代 Java 中还可以写:
String[] array =
technologies.toArray(String[]::new);
这里:
String[]::new
用于告诉 Collection:
创建一个合适大小的
String[]。
这是 JDK 11 以来可用的 toArray(IntFunction<T[]>) 形式。
方法引用会在后续 Lambda 与方法引用章节中系统学习。
3.14 Collection 通用 API 综合示例
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class CollectionApiDemo {
public static void main(String[] args) {
Collection<String> skills = new ArrayList<>();
// 添加
skills.add("Java");
skills.add("MySQL");
skills.add("Git");
skills.add("Java");
// 数量
System.out.println(skills.size());
// 是否包含
System.out.println(
skills.contains("Java")
);
// 删除
System.out.println(
skills.remove("Git")
);
// 批量添加
skills.addAll(
List.of("Spring", "Redis")
);
// 是否包含另一集合的全部元素
System.out.println(
skills.containsAll(
List.of("Java", "Spring")
)
);
// 转换数组
String[] array =
skills.toArray(String[]::new);
System.out.println(array.length);
// 条件删除
skills.removeIf(
skill -> skill.startsWith("R")
);
System.out.println(skills);
// 清空
skills.clear();
System.out.println(
skills.isEmpty()
);
}
}
这一段代码已经覆盖了 Collection 最常用的一批基础操作。
四、原理与进阶
4.1 Collection API 是统一契约
观察:
Collection<String> data = new ArrayList<>();
我们可以调用:
data.add();
data.remove();
data.contains();
data.size();
如果以后改成某个其他 Collection 实现:
Collection<String> data = new HashSet<>();
很多 Collection 层面的代码仍然可以继续使用。
这说明:
Collection 把不同数据结构的共同能力抽象成了统一接口。
这就是集合框架的重要价值:
上层代码
依赖统一接口
↓
底层可以选择不同数据结构
4.2 Collection 并不保证所有修改操作都一定支持
这是一个容易被初学者忽略的地方。
Collection 中很多修改方法属于:
optional operation
也就是说:
接口定义了这种操作,但某个具体集合实现可以选择不支持。
例如:
List<String> list =
List.of("Java", "MySQL");
得到的是不可通过常规修改方法改变内容的 List。
如果执行:
list.add("Redis");
会出现:
UnsupportedOperationException
因此不能简单理解成:
“只要是 Collection,add 就一定成功。”
更加准确的理解是:
Collection 定义统一 API
具体实现决定是否支持某些可选修改操作
4.3 contains 为什么不是简单比较地址
contains 的语义建立在对象相等判断之上。
例如:
Collection<String> names = new ArrayList<>();
names.add("Java");
System.out.println(
names.contains("Java")
);
这里需要判断:
集合中的元素
与:
传进来的对象
是否相等。
对于对象而言,这会涉及:
equals()
语义。
例如:
class Student {
private String name;
public Student(String name) {
this.name = name;
}
}
然后:
Collection<Student> students =
new ArrayList<>();
Student student = new Student("张三");
students.add(student);
System.out.println(
students.contains(student)
);
使用同一个对象引用时可以找到。
但如果:
students.contains(
new Student("张三")
);
结果是否符合“姓名相同就视为同一个学生”的业务需求,就要取决于:
Student
有没有正确建立对象相等规则。
这正是后面:
05-10 hashCode、equals 与对象去重机制
需要深入解决的问题。
当前只需要记住:
contains和remove(Object)判断自定义对象时,必须关注对象的equals语义。
4.4 remove(Object) 删除的是匹配元素
Collection 定义:
remove(Object o)
表示根据对象匹配进行删除。
但后续学习:
List
后,还会出现:
remove(int index)
因此对于:
List<Integer>
这类集合:
remove(1)
到底表示:
删除数字 1
还是:
删除索引 1
会成为一个非常典型的重载陷阱。
这个问题留到:
05-05 List 接口与有序集合
专门分析。
4.5 Collection 继承 Iterable
从接口关系看:
public interface Collection<E>
extends Iterable<E>
这意味着 Collection 天生具备:
可迭代
能力。
因此后面才能使用:
Iterator
以及增强 for 等方式遍历集合。
完整关系:
Iterable
↓
Collection
↓
List / Set / Queue ...
下一章就会正式进入:
Iterator
增强 for
forEach
五、实践应用
5.1 技术栈管理
例如保存当前掌握的技术:
Collection<String> skills =
new ArrayList<>();
可以:
skills.add("Java");
skills.add("MySQL");
skills.contains("Java");
skills.remove("MySQL");
skills.size();
5.2 黑名单批量删除
假设:
Collection<String> users
保存全部用户。
另一个:
Collection<String> blockedUsers
保存黑名单用户。
可以通过:
users.removeAll(blockedUsers);
完成一次集合运算。
5.3 求共同技能
假设两个人掌握的技术分别是:
A:
Java
MySQL
Redis
B:
Java
Python
MySQL
利用:
retainAll()
可以得到共同部分。
这种思想在:
权限计算
标签筛选
兴趣匹配
集合运算
等业务中非常常见。
六、常见问题
6.1 Collection 可以直接 new 吗?
不能。
因为:
Collection
是接口。
错误:
Collection<String> data =
new Collection<>();
常见写法:
Collection<String> data =
new ArrayList<>();
6.2 size() 与数组 length 有什么区别?
数组:
array.length
String:
string.length()
Collection:
collection.size()
这是非常典型的基础语法区别。
6.3 clear() 后集合是不是 null?
不是。
collection.clear();
表示:
集合对象仍然存在
元素数量变为 0
而:
collection = null;
表示:
当前变量不再引用原集合对象
完全不是一回事。
6.4 add() 为什么返回 boolean?
因为不同集合对新增元素有不同约束。
例如:
List
通常允许重复。
而:
Set
不允许重复元素。
因此接口通过返回:
boolean
告诉调用者:
这次操作是否改变了集合。
6.5 contains() 判断的是 == 吗?
不能简单理解成:
==
Collection 的相等匹配语义与:
equals()
密切相关。
对于自定义对象,如果业务要求:
字段相同 → 认为对象相同
通常需要正确设计对象的:
equals()
hashCode()
详细机制后续专门学习。
6.6 为什么 add() 有时候会抛 UnsupportedOperationException?
因为 Collection 中某些修改方法属于可选操作。
例如:
List<String> list =
List.of("Java", "MySQL");
不能执行普通:
list.add("Redis");
因此:
接口拥有某个方法,不代表所有实现都必须支持该修改行为。
6.7 Collection 能通过索引 get 吗?
Collection 本身没有:
get(int index)
因为并不是所有 Collection 都有“索引”概念。
例如:
List
有明确位置访问能力。
但:
Set
不是按普通 List 索引模型工作的。
因此:
get(index)
属于:
List
层面的能力,而不是 Collection 的通用能力。
这就是接口分层设计的意义。
七、练习与验收
7.1 知识问答
Collection<E>在集合体系中的定位是什么?- 为什么要先学习 Collection 通用 API,再学习 ArrayList、HashSet 等实现类?
add(E e)的返回值为什么是 boolean?size()与isEmpty()分别解决什么问题?clear()与把集合变量赋值为 null 有什么区别?contains(Object o)如何判断对象是否存在?- 为什么自定义对象使用
contains时需要关注equals? addAll与add有什么区别?removeAll与retainAll分别可以类比哪一种集合运算?toArray()为什么被称为数组 API 与集合 API 之间的桥梁?- 为什么 Collection 有
remove(Object),却没有通用的get(index)? - 为什么 Collection 中存在 optional operation?
- 什么情况下可能遇到
UnsupportedOperationException? - Collection 与 Iterable 有什么关系?
7.2 代码阅读
阅读:
Collection<String> data =
new ArrayList<>();
data.add("A");
data.add("B");
data.add("A");
System.out.println(data.size());
data.remove("A");
System.out.println(data.size());
System.out.println(data.contains("A"));
要求:
- 不运行代码,预测每一步集合状态。
remove("A")删除的是一个匹配元素还是所有匹配元素?- 最后
contains("A")是否可能仍然为 true? - 说明推理过程。
阅读:
Collection<Integer> a =
new ArrayList<>();
a.add(1);
a.add(2);
a.add(3);
a.add(4);
Collection<Integer> b =
new ArrayList<>();
b.add(2);
b.add(4);
a.retainAll(b);
System.out.println(a);
要求:
retainAll的语义是什么?- 最终 a 应该保留哪些元素?
- 它可以类比什么集合运算?
阅读:
List<String> data =
List.of("Java", "MySQL");
data.add("Redis");
要求:
- 代码能否正常完成修改?
- 如果发生异常,可能是什么异常?
- 为什么
List明明拥有add方法,当前对象却可能不支持修改?
7.3 手写代码
- 创建
Collection<String>,底层使用ArrayList。 - 添加
"Java"、"MySQL"、"Redis"、"Java"。 - 输出集合元素数量。
- 判断是否包含
"Redis"。 - 删除一个
"Java"。 - 添加
"Spring"和"Git"。 - 将集合转换为
String[]。 - 清空集合。
- 判断集合是否为空。
设计:
Collection<Student>
要求:
- 创建三个 Student 对象。
- 将三个对象加入集合。
- 判断集合是否包含原来加入的某个 Student 对象。
- 再创建一个字段内容相同的新 Student。
- 判断 contains 的结果。
- 暂时不要修改程序,只记录现象,并思考它为什么与
equals有关。
7.4 Debug
下面代码存在错误:
Collection<String> names =
new Collection<>();
names.add("张三");
要求:
- 找出错误。
- 说明为什么 Collection 不能直接实例化。
- 修改为可以正常工作的代码。
下面代码存在认知错误:
Collection<String> names =
new ArrayList<>();
names.add("张三");
String name = names.get(0);
要求:
- 判断哪一行存在问题。
- 为什么 Collection 没有
get(int index)? - 这个方法应该属于哪种集合接口?
- 不提前学习具体答案的情况下,说明接口分层设计的原因。
下面程序:
Collection<String> technologies =
new ArrayList<>();
technologies.add("Java");
technologies.add("MySQL");
technologies.clear();
System.out.println(
technologies.size()
);
某同学认为:
clear 后 technologies 就是 null
因此 size() 会产生 NullPointerException
要求:
- 判断这个说法是否正确。
- 解释
clear()的真正作用。 - 对比
technologies = null。
7.5 综合训练
开发一个简单的“个人技术栈管理器”。
初始技能:
Java
MySQL
Git
要求支持:
添加技能
删除技能
判断是否已经学习某技能
显示技能数量
批量导入技能
批量删除技能
求与另一名同学的共同技能
转换为数组
清空技能
判断是否为空
要求:
- 先写出每个需求对应的 Collection API。
- 再完成程序。
- 不使用 Stream。
- 暂时不使用 Iterator。
- 所有操作都只使用本章已经学习的 Collection 通用 API。
7.6 本章验收
闭卷完成:
- 能够手写
Collection<String> data = new ArrayList<>();。 - 能够不查看资料写出
add、remove、contains、size、isEmpty、clear。 - 能够解释
addAll、removeAll、retainAll、containsAll。 - 能够把
Collection<String>转换成String[]。 - 能够解释为什么 Collection 没有通用的索引访问 API。
- 能够解释
contains与equals的关系。 - 能够解释什么是 optional operation。
- 能够解释为什么同一个 Collection API 可以作用于多种不同集合实现。
如果这些问题仍然需要依赖 IDE 自动补全才能完成,那么本章还没有达到真正掌握的标准。