一、为什么需要集合?
数组的痛点
// 用数组存储学生
Student\[\] students = new Student3;
students0 = new Student("张三", 18);
students1 = new Student("李四", 19);
students2 = new Student("王五", 20);
// 问题来了:
// 1. 想再加一个学生?❌ 数组满了,只能重新创建更大的数组,手动拷贝
// 2. 想删除"李四"?❌ 要手动把后面的元素往前移
// 3. 想知道有几个学生?❌ 得自己维护一个计数器
// 4. 想查找"张三"在哪?❌ 得自己写循环遍历
数组就像固定大小的格子柜 ------ 买小了不够用,买大了浪费空间,中途换柜子还得自己搬东西。
集合的优势
集合(Collection)是 Java 提供的一套容器框架,可以动态存储、管理一组对象。
| 对比项 | 数组 | 集合(ArrayList) |
|---|---|---|
| 长度 | 固定,创建时指定 | 动态扩容,想加就加 |
| 增删元素 | 手动拷贝、移位 | 一行代码搞定 |
| 查找 | 手动遍历 | 内置方法(contains、indexOf) |
| 类型安全 | 可以存基本类型 | 只能存对象(JDK5+ 有自动装箱) |
| 功能 | 只有 length | 几十种实用方法 |
二、ArrayList ------ 动态数组
什么是 ArrayList?
ArrayList 是 List 接口的实现类,底层是一个可以自动扩容的数组。
生活类比:
ArrayList 就像一个可以自动伸缩的购物袋:
├── 买东西少 → 袋子收缩,不占地方
├── 买东西多 → 袋子自动变大,装得下
├── 想拿第3个商品 → 直接伸手进去拿(按索引访问,快!)
└── 想加商品 → 直接往里放(自动扩容)
创建 ArrayList
import java.util.ArrayList;
import java.util.List;
// 方式1:推荐写法(面向接口)
List<String> list1 = new ArrayList<>();
// 方式2:直接写实现类
ArrayList<Integer> list2 = new ArrayList<>();
// 方式3:指定初始容量(知道大概要存多少时)
ArrayList<String> list3 = new ArrayList<>(20);
⚠️ 泛型:<String> 表示这个 ArrayList 只能存 String 类型的对象。这是类型安全的保障。
ArrayList 常用方法大全
| 方法 | 说明 | 示例 |
|---|---|---|
| add(E e) | 在末尾添加元素 | list.add("张三") |
| add(int index, E e) | 在指定位置插入 | list.add(0, "李四") |
| get(int index) | 获取指定位置元素 | list.get(0) |
| set(int index, E e) | 修改指定位置元素 | list.set(0, "王五") |
| remove(int index) | 删除指定位置元素 | list.remove(0) |
| remove(Object o) | 删除指定元素 | list.remove("张三") |
| size() | 获取元素个数 | list.size() |
| isEmpty() | 判断是否为空 | list.isEmpty() |
| contains(Object o) | 判断是否包含某元素 | list.contains("张三") |
| indexOf(Object o) | 查找元素位置 | list.indexOf("张三") |
| clear() | 清空所有元素 | list.clear() |
| toArray() | 转为数组 | list.toArray() |
遍历 ArrayList 的三种方式
ArrayList<String> list = new ArrayList<>();
list.add("苹果");
list.add("香蕉");
list.add("橙子");
// 方式1:普通 for 循环(需要索引时用)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 方式2:增强 for 循环(最常用,简洁)
for (String fruit : list) {
System.out.println(fruit);
}
// 方式3:迭代器(后面学,了解即可)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
ArrayList 底层原理(通俗版)
初始状态:内部数组容量 = 10(默认)
null, null, null, null, null, null, null, null, null, null
添加第1个元素 "张三":
"张三", null, null, ...
添加第11个元素时:
内部数组满了 → 自动创建新数组(容量变为 15 = 10 + 10/2)
→ 把旧数组元素拷贝到新数组 → 继续添加
扩容机制 :默认初始容量 10,满了之后扩容为原来的 1.5 倍。
三、编码实战:学生管理系统(ArrayList CRUD)
需求
功能清单:
-
添加学生(Create)
-
查看所有学生(Read)
-
修改学生信息(Update)
-
删除学生(Delete)
-
按学号查找学生
-
退出系统
完整代码
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// ========== 学生类 ==========
class Student {
private String id; // 学号
private String name; // 姓名
private int age; // 年龄
private String major; // 专业
public Student() {
}
public Student(String id, String name, int age, String major) {
this.id = id;
this.name = name;
this.age = age;
this.major = major;
}
// Getter 和 Setter
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getMajor() { return major; }
public void setMajor(String major) { this.major = major; }
// 重写 toString(),方便打印
@Override
public String toString() {
return "学号:" + id + ",姓名:" + name + ",年龄:" + age + ",专业:" + major;
}
}
// ========== 学生管理类(核心 CRUD) ==========
class StudentManager {
// 用 ArrayList 存储学生
private List<Student> students = new ArrayList<>();
// 1. 添加学生
public boolean addStudent(Student student) {
// 检查学号是否已存在
for (Student s : students) {
if (s.getId().equals(student.getId())) {
System.out.println("❌ 学号 " + student.getId() + " 已存在!");
return false;
}
}
students.add(student);
System.out.println("✅ 添加成功!");
return true;
}
// 2. 查看所有学生
public void showAllStudents() {
if (students.isEmpty()) {
System.out.println("📭 暂无学生信息");
return;
}
System.out.println("\n====== 学生列表(共 " + students.size() + " 人)======");
for (int i = 0; i < students.size(); i++) {
System.out.println((i + 1) + ". " + students.get(i));
}
System.out.println("========================================\n");
}
// 3. 按学号查找学生
public Student findById(String id) {
for (Student s : students) {
if (s.getId().equals(id)) {
return s;
}
}
return null;
}
// 4. 修改学生信息
public boolean updateStudent(String id, String newName, int newAge, String newMajor) {
Student student = findById(id);
if (student == null) {
System.out.println("❌ 未找到学号为 " + id + " 的学生");
return false;
}
if (newName != null && !newName.trim().isEmpty()) {
student.setName(newName);
}
if (newAge > 0) {
student.setAge(newAge);
}
if (newMajor != null && !newMajor.trim().isEmpty()) {
student.setMajor(newMajor);
}
System.out.println("✅ 修改成功!");
return true;
}
// 5. 删除学生
public boolean deleteStudent(String id) {
Student student = findById(id);
if (student == null) {
System.out.println("❌ 未找到学号为 " + id + " 的学生");
return false;
}
students.remove(student);
System.out.println("✅ 删除成功!");
return true;
}
// 6. 获取学生总数
public int getCount() {
return students.size();
}
}
// ========== 测试类 ==========
public class StudentManagementSystem {
public static void main(String\[\] args) {
StudentManager manager = new StudentManager();
Scanner scanner = new Scanner(System.in);
// 预添加几个学生
manager.addStudent(new Student("S001", "张三", 20, "计算机科学"));
manager.addStudent(new Student("S002", "李四", 21, "软件工程"));
manager.addStudent(new Student("S003", "王五", 19, "人工智能"));
while (true) {
System.out.println("\n====== 🎓 学生管理系统 ======");
System.out.println("1. 添加学生");
System.out.println("2. 查看所有学生");
System.out.println("3. 查找学生(按学号)");
System.out.println("4. 修改学生信息");
System.out.println("5. 删除学生");
System.out.println("6. 退出");
System.out.print("请选择操作:");
int choice = scanner.nextInt();
scanner.nextLine(); // 消耗换行符
switch (choice) {
case 1:
System.out.print("请输入学号:");
String id = scanner.nextLine();
System.out.print("请输入姓名:");
String name = scanner.nextLine();
System.out.print("请输入年龄:");
int age = scanner.nextInt();
scanner.nextLine();
System.out.print("请输入专业:");
String major = scanner.nextLine();
manager.addStudent(new Student(id, name, age, major));
break;
case 2:
manager.showAllStudents();
break;
case 3:
System.out.print("请输入要查找的学号:");
String findId = scanner.nextLine();
Student s = manager.findById(findId);
if (s != null) {
System.out.println("✅ 找到:" + s);
} else {
System.out.println("❌ 未找到该学生");
}
break;
case 4:
System.out.print("请输入要修改的学生学号:");
String updateId = scanner.nextLine();
System.out.print("请输入新姓名(不修改直接回车):");
String newName = scanner.nextLine();
System.out.print("请输入新年龄(不修改输入0):");
int newAge = scanner.nextInt();
scanner.nextLine();
System.out.print("请输入新专业(不修改直接回车):");
String newMajor = scanner.nextLine();
manager.updateStudent(updateId, newName, newAge, newMajor);
break;
case 5:
System.out.print("请输入要删除的学生学号:");
String deleteId = scanner.nextLine();
manager.deleteStudent(deleteId);
break;
case 6:
System.out.println("👋 再见!");
scanner.close();
System.exit(0);
default:
System.out.println("❌ 无效选择,请重新输入");
}
}
}
}
运行效果示例
====== 🎓 学生管理系统 ======
-
添加学生
-
查看所有学生
-
查找学生(按学号)
-
修改学生信息
-
删除学生
-
退出
请选择操作:2
====== 学生列表(共 3 人)======
-
学号:S001,姓名:张三,年龄:20,专业:计算机科学
-
学号:S002,姓名:李四,年龄:21,专业:软件工程
-
学号:S003,姓名:王五,年龄:19,专业:人工智能
========================================
核心知识点解析
| 要点 | 说明 |
|---|---|
| List<Student> | 面向接口编程,用接口类型引用 ArrayList |
| students.add() | 动态添加,无需关心数组大小 |
| students.remove() | 传入对象直接删除,ArrayList 自动移位 |
| students.isEmpty() | 判断是否为空,避免空指针 |
| findById() | 遍历查找,返回 null 表示没找到 |
| toString() 重写 | System.out.println(student) 时自动调用 |
四、LinkedList ------ 双向链表
什么是 LinkedList?
LinkedList 是 List 接口的另一个实现类,底层是双向链表。
生活类比:
ArrayList 像一排连续的小格子,每个格子有编号,找第n个直接定位(快)
但中间插入/删除要挪动后面所有元素(慢)
LinkedList 像一列火车,每节车厢连着前后
中间插入/删除只需改连接(快)
但找第n节要从头/尾开始数(慢)
LinkedList 的独门绝技:队列和栈
LinkedList 除了实现 List 接口,还实现了 Queue(队列)接口,天然支持队列和栈操作。
模拟队列(FIFO:先进先出)
队列就像排队买票:先来的人先买,后来的人站后面。
import java.util.LinkedList;
public class QueueDemo {
public static void main(String\[\] args) {
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.offer("张三");
queue.offer("李四");
queue.offer("王五");
System.out.println("队列内容:" + queue);
// 输出:张三, 李四, 王五
// 查看队首(不删除)
System.out.println("队首是谁:" + queue.peek());
// 输出:张三
// 出队
String first = queue.poll();
System.out.println("出队:" + first);
// 输出:出队:张三
System.out.println("出队后队列:" + queue);
// 输出:李四, 王五
// 继续出队直到空
System.out.println("出队:" + queue.poll()); // 李四
System.out.println("出队:" + queue.poll()); // 王五
System.out.println("出队(空队列):" + queue.poll()); // null(不会抛异常)
}
}
模拟栈(LIFO:后进先出)
栈就像一摞盘子:只能从最上面放、从最上面取。
import java.util.LinkedList;
public class StackDemo {
public static void main(String\[\] args) {
LinkedList<Integer> stack = new LinkedList<>();
// 入栈(push)
stack.push(10);
stack.push(20);
stack.push(30);
System.out.println("栈内容:" + stack);
// 输出:30, 20, 10 (30在栈顶)
// 查看栈顶(不弹出)
System.out.println("栈顶元素:" + stack.peek());
// 输出:30
// 出栈(pop)
int top = stack.pop();
System.out.println("出栈:" + top);
// 输出:出栈:30
System.out.println("出栈后栈内容:" + stack);
// 输出:20, 10
// 继续出栈
System.out.println("出栈:" + stack.pop()); // 20
System.out.println("出栈:" + stack.pop()); // 10
// stack.pop(); // ❌ 空栈时调用会抛 NoSuchElementException
System.out.println("栈是否为空:" + stack.isEmpty()); // true
}
}
LinkedList 作为 List 使用
LinkedList<String> list = new LinkedList<>();
list.add("A"); // 尾部添加
list.addFirst("B"); // 头部添加
list.addLast("C"); // 尾部添加(同 add)
list.add(1, "D"); // 在索引1处插入
System.out.println(list.get(0)); // B
System.out.println(list.getFirst()); // B
System.out.println(list.getLast()); // C
list.removeFirst(); // 删除头部
list.removeLast(); // 删除尾部
LinkedList 常用方法速查
| 方法 | 说明 | 队列/栈角色 |
|---|---|---|
| add(e) / addLast(e) | 尾部添加 | 入队 / 入栈 |
| addFirst(e) | 头部添加 | 入栈 |
| offer(e) | 尾部添加(推荐队列用) | 入队 |
| poll() | 删除并返回头部 | 出队 |
| peek() | 查看头部(不删除) | 查看队首/栈顶 |
| push(e) | 头部压入 | 入栈 |
| pop() | 删除并返回头部 | 出栈 |
| removeFirst() | 删除头部 | 出栈/出队 |
| removeLast() | 删除尾部 | - |
| getFirst() / getLast() | 获取头/尾 | - |
五、ArrayList vs LinkedList ------ 到底用哪个?
对比速查表
| 维度 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问 get(i) | ⚡ O(1) 极快 | 🐢 O(n) 慢 |
| 尾部添加 add(e) | ⚡ 平均 O(1) | ⚡ O(1) |
| 中间插入 add(i, e) | 🐢 O(n) 要移位 | ⚡ O(1)(找到位置后) |
| 中间删除 remove(i) | 🐢 O(n) 要移位 | ⚡ O(1)(找到位置后) |
| 内存占用 | 较少(只存数据) | 较多(每个节点额外存前后指针) |
| 适用场景 | 查询多、增删少 | 增删多、查询少 |
| 额外功能 | 无 | 天然支持队列、栈操作 |
选择指南
问自己:
"我的程序主要是做什么?"
├── 大量按索引查找、遍历 → ArrayList ✅
├── 大量在中间插入/删除 → LinkedList ✅
├── 需要当队列/栈用 → LinkedList ✅
└── 不确定 → 默认用 ArrayList ✅(90% 场景选它)
六、每日一练
练习1(基础):ArrayList 练习
- 创建一个 ArrayList<String> 存储 5 个你喜欢的电影名
- 在第 2 个位置插入一部新电影
- 删除最后一部电影
- 遍历打印所有电影名
练习2(进阶):图书管理系统
- 定义 Book 类(书名、作者、价格)
- 用 ArrayList<Book> 存储图书
- 实现:添加图书、按书名查找、修改价格、删除图书、显示所有图书
练习3(挑战):LinkedList 综合
- 用 LinkedList 模拟一个"浏览器历史记录"
- 访问新页面 → push() 入栈
- 点击"后退" → pop() 出栈
- 显示当前页面 → peek()
- 显示所有历史记录
- 同时用同一个 LinkedList 模拟一个"打印任务队列"
- 添加打印任务 → offer()
- 执行打印 → poll()
- 查看下一个任务 → peek()
一句话记住本章:ArrayList 是"查得快"的动态数组,LinkedList 是"改得快"的双向链表;ArrayList 适合日常 CRUD,LinkedList 适合队列和栈。
需要我提供课后练习的完整解答,还是想继续深入下一个知识点(如HashMap 、异常处理 、泛型深入)?随时告诉我!