Skip to content

第10章:核心集合

数组知道怎么用了、面向对象会了——现在是时候用真正的集合框架了。

📌 代码说明:标注"可复制运行"的代码块包含完整的 main 方法。没标注的是概念示意片段。

你将学会
ArrayList·HashMap·HashSet
前置知识
第6章 数组 + 第8章 继承
预计时间
35 分钟
需要准备
JDK + 编辑器
完成标志
用集合框架管理内存数据

一个故事开头

你的奶茶店上了一个新系统:每个顾客可以注册会员。第一天来了 5 个人,一个月后来了 500 人。

如果你用数组来存会员名单:

java
String[] members = new String[5];   // 定死了只能存5个
// 第6个人来了怎么办?

数组的三大问题:

  1. 长度固定——你永远不知道以后有多少人
  2. 中间插入/删除麻烦——要手动移动后面所有元素
  3. 只能按索引找——想"根据名字找手机号"要自己写循环

集合(Collection)就是解决这些问题的。 它们会自动扩缩容,提供增删查改的现成方法。


1. ArrayList——自动扩容的数组

你需要它的时候:元素数量会变、需要按索引访问、需要频繁在末尾添加。

java
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 和数组的关系

java
// 数组 → 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开始的连续整数)。

java
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());
        }
    }
}

现实用法

java
// 奶茶店菜单
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——不许重复

你需要它的时候:确保元素不重复,或者快速判断某个东西是否存在。

java
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. equalshashCode——集合靠它们工作

你可能会好奇:HashMap 是怎么找到"Alice"对应的 95 的?HashSet 怎么知道"Alice"已经存在了?

答案:equals()hashCode() 方法。

java
// 默认的 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();
    }
}

现在:

java
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 可以自动生成 equalshashCode。选好参与计算的字段,点"Generate"就行。


5. 完整例子:奶茶店会员管理系统

java
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);
        }
    }
}

本章小结

  1. ArrayList——自动扩容的数组,按索引访问,尾端增删快
  2. HashMap——键值对存储,按键查值快
  3. HashSet——元素不重复,快速判断存在性
  4. equals + hashCode——集合靠它们判断元素相等,必须一起重写
  5. 三选一:索引用途→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?去重效果。


下一篇

第11章 异常处理

用 ❤️ 构建 | Software Systems Atlas