第10章:核心集合
数组知道怎么用了、面向对象会了——现在是时候用真正的集合框架了。
📌 代码说明:标注"可复制运行"的代码块包含完整的
main方法。没标注的是概念示意片段。
一个故事开头
你的奶茶店上了一个新系统:每个顾客可以注册会员。第一天来了 5 个人,一个月后来了 500 人。
如果你用数组来存会员名单:
String[] members = new String[5]; // 定死了只能存5个
// 第6个人来了怎么办?数组的三大问题:
- 长度固定——你永远不知道以后有多少人
- 中间插入/删除麻烦——要手动移动后面所有元素
- 只能按索引找——想"根据名字找手机号"要自己写循环
集合(Collection)就是解决这些问题的。 它们会自动扩缩容,提供增删查改的现成方法。
1. ArrayList——自动扩容的数组
你需要它的时候:元素数量会变、需要按索引访问、需要频繁在末尾添加。
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
// 创建
List<String> names = new ArrayList<>();
// 增
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add(1, "David"); // 插入到索引1
// [Alice, David, Bob, Charlie]
// 查
System.out.println(names.get(0)); // "Alice"
System.out.println(names.indexOf("Bob")); // 2
System.out.println(names.contains("Eve")); // false
System.out.println(names.size()); // 4
// 改
names.set(0, "Eve"); // [Eve, David, Bob, Charlie]
// 删
names.remove(1); // 删除索引1 → [Eve, Bob, Charlie]
names.remove("Bob"); // 按内容删第一个匹配的 → [Eve, Charlie]
// 遍历
for (String name : names) {
System.out.println(name);
}
// 清空
names.clear();
System.out.println(names.isEmpty()); // true
}
}ArrayList 和数组的关系
// 数组 → ArrayList
String[] arr = {"A", "B", "C"};
List<String> list = new ArrayList<>(Arrays.asList(arr));
// ArrayList → 数组
String[] back = list.toArray(new String[0]);2. HashMap——按键找值
你需要它的时候:想通过一个"键"快速找到对应的"值"。
比如学员名单:输入学号能查到姓名。数组做不到"按学号查"(学号不是从0开始的连续整数)。
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
// 创建:键是 String,值是 Integer
Map<String, Integer> scores = new HashMap<>();
// 增/改
scores.put("Alice", 95);
scores.put("Bob", 88);
scores.put("Charlie", 92);
scores.put("Alice", 98); // Alice 已存在,更新值
// 查
System.out.println(scores.get("Alice")); // 98
System.out.println(scores.get("David")); // null(没有这个键)
System.out.println(scores.getOrDefault("David", 0)); // 0(没有就返回默认值)
System.out.println(scores.containsKey("Bob")); // true
System.out.println(scores.size()); // 3
// 删
scores.remove("Bob");
// 遍历
for (String name : scores.keySet()) {
System.out.println(name + ": " + scores.get(name));
}
// 用 entrySet 遍历(更高效)
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
}
}现实用法
// 奶茶店菜单
Map<String, Double> menu = new HashMap<>();
menu.put("波波奶茶", 15.0);
menu.put("杨枝甘露", 18.0);
menu.put("柠檬茶", 12.0);
// 顾客点单
String order = "波波奶茶";
double price = menu.getOrDefault(order, 0.0);
if (price > 0) {
System.out.println(order + " 价格: " + price + "元");
} else {
System.out.println("没有这个饮品");
}3. HashSet——不许重复
你需要它的时候:确保元素不重复,或者快速判断某个东西是否存在。
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<String> vipMembers = new HashSet<>();
vipMembers.add("Alice");
vipMembers.add("Bob");
vipMembers.add("Alice"); // 第二次添加——被忽略(已存在)
vipMembers.add("Charlie");
System.out.println(vipMembers.size()); // 3(不是4,因为 Alice 只存一次)
System.out.println(vipMembers.contains("Alice")); // true
System.out.println(vipMembers.contains("David")); // false
// 没有 get()——你不能问"索引3是什么"
// 遍历
for (String member : vipMembers) {
System.out.println(member);
}
}
}什么时候用哪个
| 场景 | 用哪个 |
|---|---|
| 按索引访问、尾端增删多 | ArrayList |
| 按键快速查找值 | HashMap |
| 快速判断是否存在、去重 | HashSet |
| 既需要快速查找又需要保持顺序 | LinkedHashMap / TreeMap(后面可以查) |
4. equals 和 hashCode——集合靠它们工作
你可能会好奇:HashMap 是怎么找到"Alice"对应的 95 的?HashSet 怎么知道"Alice"已经存在了?
答案:靠 equals() 和 hashCode() 方法。
// 默认的 equals 比较的是引用(类似 ==)
Student s1 = new Student("Alice");
Student s2 = new Student("Alice");
System.out.println(s1.equals(s2)); // false(默认实现!两个不同对象)
// 你需要重写 equals 和 hashCode
class Student {
String name;
Student(String name) { this.name = name; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student other = (Student) obj;
return name.equals(other.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}现在:
Set<Student> set = new HashSet<>();
set.add(new Student("Alice"));
set.add(new Student("Alice"));
System.out.println(set.size()); // 1(因为两个"Alice" equals 相等)规则:如果两个对象 equals 相等,它们的 hashCode 必须相等。反过来不要求——hashCode 相等不意味着 equals 相等。
日常开发中,你的 IDE 可以自动生成 equals 和 hashCode。选好参与计算的字段,点"Generate"就行。
5. 完整例子:奶茶店会员管理系统
import java.util.*;
record Member(String name, String phone, int points) {}
public class MemberSystem {
public static void main(String[] args) {
// 会员名单(不重复)
Set<Member> allMembers = new HashSet<>();
allMembers.add(new Member("Alice", "13800138001", 100));
allMembers.add(new Member("Bob", "13800138002", 50));
// 积分排行榜(按名字查积分)
Map<String, Integer> pointsMap = new HashMap<>();
for (Member m : allMembers) {
pointsMap.put(m.name(), m.points());
}
// 查询积分
String searchName = "Alice";
int points = pointsMap.getOrDefault(searchName, 0);
System.out.println(searchName + " 的积分: " + points);
// 最近10条订单记录
List<String> recentOrders = new ArrayList<>();
recentOrders.add("波波奶茶");
recentOrders.add("柠檬茶");
recentOrders.add("杨枝甘露");
recentOrders.add(0, "抹茶拿铁"); // 插到最前面
// [抹茶拿铁, 波波奶茶, 柠檬茶, 杨枝甘露]
System.out.println("最近订单:");
for (String order : recentOrders) {
System.out.println(" " + order);
}
}
}本章小结
- ArrayList——自动扩容的数组,按索引访问,尾端增删快
- HashMap——键值对存储,按键查值快
- HashSet——元素不重复,快速判断存在性
equals+hashCode——集合靠它们判断元素相等,必须一起重写- 三选一:索引用途→ArrayList,查值→HashMap,去重→HashSet
✅ 验收标准
完成本章后,你应该能:
- [ ] 用 ArrayList 存储和遍历对象
- [ ] 用 HashMap 实现键值对查找
- [ ] 用 HashSet 实现去重
- [ ] 重写
equals()和hashCode()让集合正确工作 - [ ] 在泛型集合中遍历元素
📌 常见卡点
- 用 `==` 比较集合里的元素——总用 `equals()`
- 遍历时删除元素用 Iterator.remove() 而不是 list.remove()
- HashMap 的 key 必须正确实现 equals+hashCode
- 泛型不写会报警告——总写
ArrayList<String>而不是裸 ArrayList
🔜 现在不需要理解
- ArrayList vs LinkedList 的性能差异
- ConcurrentModificationException 的细节
- TreeMap/TreeSet 的自然排序
- Collections.synchronizedXXX——用并发集合代替
🧪 练习
1. ArrayList:创建一个 ArrayList<String> 存放一周七天的名字,遍历输出。
2. HashMap:创建一个 Map<String, Double> 存放 3 种奶茶的价格。查询"波波奶茶"的价格,如果不存在给出默认值。
3. HashSet:创建一个 Set<Integer>,添加数字 1,2,3,4,5,3,2,1(故意重复)。输出集合的 size——看看是不是 5?去重效果。