在前七天的学习中,我们掌握了 Java 基础语法、面向对象特性、异常处理等核心知识。今天我们将深入学习Java 集合框架(Collection Framework)—— 这是 Java 提供的一套用于存储和操作多个数据的工具类。在 JavaWeb 开发中,集合框架被广泛用于处理批量数据(如查询结果集、表单提交的多条数据等),掌握它能极大提高数据处理效率。
为什么需要集合框架?
数组作为 Java 中最基础的数据容器,存在诸多局限性:
- 长度固定,无法动态扩展
- 只能存储相同类型的元素
- 缺乏常用操作(如添加、删除、查找等)的内置方法
集合框架正是为解决这些问题而设计的,它具有:
- 动态扩展:可以根据需要自动调整大小
- 丰富的操作:内置添加、删除、查找、排序等方法
- 多数据结构:提供多种数据结构(列表、集合、映射等)满足不同需求
- 泛型支持:编译时类型检查,避免类型转换错误
集合框架体系结构
Java 集合框架主要分为两大体系:
Collection 接口:存储单个元素的集合
- List:有序、可重复的集合(如ArrayList、LinkedList)
- Set:无序、不可重复的集合(如HashSet、TreeSet)
- Queue:队列(先进先出,如LinkedList、PriorityQueue)
Map 接口:存储键值对(key-value)的集合(如HashMap、TreeMap)
体系结构简图:
java.util
├─ Collection(接口)
│ ├─ List(接口)
│ │ ├─ ArrayList(实现类)
│ │ ├─ LinkedList(实现类)
│ │ └─ Vector(实现类,线程安全)
│ │
│ ├─ Set(接口)
│ │ ├─ HashSet(实现类)
│ │ ├─ TreeSet(实现类,有序)
│ │ └─ LinkedHashSet(实现类,保持插入顺序)
│ │
│ └─ Queue(接口)
│ └─ LinkedList(实现类)
│
└─ Map(接口)
├─ HashMap(实现类)
├─ TreeMap(实现类,按键排序)
├─ LinkedHashMap(实现类,保持插入顺序)
└─ Hashtable(实现类,线程安全)
泛型:集合的类型安全保障
在 JDK 5 之前,集合可以存储任意类型的对象,取出时需要强制类型转换,容易出现ClassCastException。泛型(Generic)解决了这个问题,它允许在定义集合时指定元素类型:
// 定义泛型集合(只能存储String类型)
List<String> stringList = new ArrayList<String>();
// JDK 7+可以简化为
List<String> stringList = new ArrayList<>();
泛型的优势:
- 编译时类型检查,避免类型错误
- 无需强制类型转换,代码更简洁
- 提高代码的可读性和可维护性
List 接口:有序可重复的集合
List接口是 Collection 的子接口,具有以下特点:
- 元素有序(按插入顺序排列)
- 元素可重复
- 可以通过索引访问元素(类似数组)
1. ArrayList:基于动态数组的实现
ArrayList是最常用的 List 实现类,底层使用动态数组存储元素:
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
// 创建ArrayList(存储String类型)
List<String> fruits = new ArrayList<>();
// 1. 添加元素
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("橙子");
fruits.add(1, "葡萄"); // 在索引1处插入元素
System.out.println("添加后:" + fruits); // [苹果, 葡萄, 香蕉, 橙子]
// 2. 获取元素
String first = fruits.get(0);
System.out.println("第一个元素:" + first); // 苹果
// 3. 修改元素
fruits.set(2, "西瓜");
System.out.println("修改后:" + fruits); // [苹果, 葡萄, 西瓜, 橙子]
// 4. 删除元素
fruits.remove(3); // 按索引删除
fruits.remove("葡萄"); // 按元素删除
System.out.println("删除后:" + fruits); // [苹果, 西瓜]
// 5. 遍历元素
System.out.println("遍历元素:");
// 方式1:for循环
for (int i = 0; i < fruits.size(); i++) {
System.out.println(fruits.get(i));
}
// 方式2:增强for循环
for (String fruit : fruits) {
System.out.println(fruit);
}
// 6. 其他常用方法
System.out.println("是否包含苹果:" + fruits.contains("苹果")); // true
System.out.println("集合大小:" + fruits.size()); // 2
System.out.println("是否为空:" + fruits.isEmpty()); // false
// 清空集合
fruits.clear();
System.out.println("清空后:" + fruits); // []
}
}
ArrayList的特点:
- 查找和修改快(通过索引直接访问)
- 添加和删除慢(需要移动大量元素)
- 初始容量为 10,当元素满时自动扩容为原来的 1.5 倍
2. LinkedList:基于双向链表的实现
LinkedList底层使用双向链表存储元素,适合频繁添加和删除的场景:
import java.util.LinkedList;
import java.util.List;
public class LinkedListDemo {
public static void main(String[] args) {
List<Integer> numbers = new LinkedList<>();
// 添加元素
numbers.add(10);
numbers.add(20);
numbers.addFirst(5); // 添加到头部
numbers.addLast(30); // 添加到尾部
System.out.println("LinkedList内容:" + numbers); // [5, 10, 20, 30]
// 链表特有的方法
System.out.println("第一个元素:" + numbers.getFirst()); // 5
System.out.println("最后一个元素:" + numbers.getLast()); // 30
numbers.removeFirst(); // 删除头部元素
numbers.removeLast(); // 删除尾部元素
System.out.println("操作后:" + numbers); // [10, 20]
// 作为队列使用(先进先出)
LinkedList<String> queue = new LinkedList<>();
queue.offer("A"); // 入队
queue.offer("B");
queue.offer("C");
System.out.println("出队元素:" + queue.poll()); // A
System.out.println("队列剩余:" + queue); // [B, C]
}
}
LinkedList的特点:
- 添加和删除快(只需修改指针指向)
- 查找和修改慢(需要从头遍历)
- 实现了 Queue 接口,可作为队列使用
3. ArrayList vs LinkedList 如何选择?
频繁查询和修改 | ArrayList | 基于数组,索引访问效率高 |
频繁添加和删除 | LinkedList | 基于链表,操作节点效率高 |
存储大量数据 | ArrayList | 内存占用更紧凑 |
需要作为队列使用 | LinkedList | 实现了 Queue 接口 |
Set 接口:无序不可重复的集合
Set接口是 Collection 的子接口,具有以下特点:
- 元素无序(存储顺序和取出顺序可能不同)
- 元素不可重复(通过equals()方法判断)
- 没有索引,不能通过索引访问元素
1. HashSet:基于哈希表的实现
HashSet是最常用的 Set 实现类,底层基于哈希表(HashMap)实现:
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<String> cities = new HashSet<>();
// 添加元素(自动去重)
cities.add("北京");
cities.add("上海");
cities.add("广州");
cities.add("北京"); // 重复元素,添加失败
System.out.println("HashSet内容:" + cities); // [上海, 广州, 北京](顺序不确定)
// 删除元素
cities.remove("广州");
System.out.println("删除后:" + cities); // [上海, 北京]
// 遍历元素(没有索引,不能用普通for循环)
System.out.println("遍历元素:");
for (String city : cities) {
System.out.println(city);
}
// 其他常用方法
System.out.println("是否包含上海:" + cities.contains("上海")); // true
System.out.println("集合大小:" + cities.size()); // 2
}
}
HashSet去重原理:
注意:自定义类存储到 HashSet 时,必须重写hashCode()和equals()方法,否则无法正确去重:
import java.util.Objects;
public class Student {
private String id;
private String name;
// 构造方法、getter、setter省略
// 重写hashCode和equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id); // 按学号判断是否相同
}
@Override
public int hashCode() {
return Objects.hash(id); // 基于学号计算哈希值
}
}
2. TreeSet:有序的 Set 集合
TreeSet底层基于红黑树实现,能够对元素进行排序:
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
// 存储整数(自动排序)
Set<Integer> nums = new TreeSet<>();
nums.add(30);
nums.add(10);
nums.add(20);
nums.add(10); // 重复元素,不存储
System.out.println("排序后的整数:" + nums); // [10, 20, 30]
// 存储字符串(按自然顺序排序)
Set<String> words = new TreeSet<>();
words.add("banana");
words.add("apple");
words.add("cherry");
System.out.println("排序后的字符串:" + words); // [apple, banana, cherry]
}
}
TreeSet排序方式:
- 自然排序:元素实现Comparable接口,重写compareTo()方法
- 定制排序:创建 TreeSet 时传入Comparator比较器
实例:自定义对象的 TreeSet 排序
import java.util.Set;
import java.util.TreeSet;
// 学生类实现Comparable接口
class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 按年龄排序
@Override
public int compareTo(Student o) {
return this.age – o.age; // 正数:当前对象大;负数:当前对象小;0:相等
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public class TreeSetCustomSort {
public static void main(String[] args) {
Set<Student> students = new TreeSet<>();
students.add(new Student("张三", 20));
students.add(new Student("李四", 18));
students.add(new Student("王五", 22));
System.out.println("按年龄排序:" + students); // [李四(18), 张三(20), 王五(22)]
}
}
Map 接口:键值对集合
Map接口用于存储键值对(key-value)映射,具有以下特点:
- 键(key)不可重复,值(value)可以重复
- 每个键对应一个值,通过键可以快速查找值
- 常用实现类:HashMap、TreeMap、LinkedHashMap
1. HashMap:基于哈希表的实现
HashMap是最常用的 Map 实现类,底层基于哈希表实现:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapDemo {
public static void main(String[] args) {
// 创建HashMap(键:String,值:Integer)
Map<String, Integer> scores = new HashMap<>();
// 1. 添加键值对
scores.put("张三", 90);
scores.put("李四", 85);
scores.put("王五", 95);
scores.put("张三", 92); // 键相同,覆盖值
System.out.println("HashMap内容:" + scores); // {李四=85, 张三=92, 王五=95}(顺序不确定)
// 2. 获取值
int zhangSanScore = scores.get("张三");
System.out.println("张三的分数:" + zhangSanScore); // 92
// 3. 判断是否包含键/值
System.out.println("是否包含李四:" + scores.containsKey("李四")); // true
System.out.println("是否包含100分:" + scores.containsValue(100)); // false
// 4. 删除键值对
scores.remove("王五");
System.out.println("删除后:" + scores); // {李四=85, 张三=92}
// 5. 遍历Map
System.out.println("遍历键:");
Set<String> keys = scores.keySet(); // 获取所有键
for (String key : keys) {
System.out.println(key + ":" + scores.get(key));
}
System.out.println("遍历键值对:");
Set<Map.Entry<String, Integer>> entries = scores.entrySet(); // 获取所有键值对
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 6. 其他常用方法
System.out.println("集合大小:" + scores.size()); // 2
scores.clear(); // 清空集合
System.out.println("清空后:" + scores); // {}
}
}
HashMap的特点:
- 查找、添加、删除效率高(基于哈希表)
- 键无序(存储顺序和取出顺序可能不同)
- 键可以为 null(但只能有一个 null 键)
- JDK 8 + 中,当链表长度超过 8 时,会转为红黑树提高性能
2. TreeMap:有序的 Map 集合
TreeMap底层基于红黑树实现,能够按键进行排序:
import java.util.Map;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
// TreeMap会自动按键排序
Map<String, String> dictionary = new TreeMap<>();
dictionary.put("banana", "香蕉");
dictionary.put("apple", "苹果");
dictionary.put("cherry", "樱桃");
System.out.println("按键排序:" + dictionary);
// 输出:{apple=苹果, banana=香蕉, cherry=樱桃}
}
}
3. LinkedHashMap:保持插入顺序的 Map
LinkedHashMap是HashMap的子类,能够保持键值对的插入顺序:
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("张三", 20);
map.put("李四", 18);
map.put("王五", 22);
// 输出顺序与插入顺序一致
System.out.println("LinkedHashMap内容:" + map);
// 输出:{张三=20, 李四=18, 王五=22}
}
}
集合工具类:Collections
java.util.Collections类提供了大量静态方法,用于操作集合:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
// 排序
Collections.sort(list);
System.out.println("排序后:" + list); // [1, 2, 3]
// 反转
Collections.reverse(list);
System.out.println("反转后:" + list); // [3, 2, 1]
// 查找最大值
int max = Collections.max(list);
System.out.println("最大值:" + max); // 3
// 填充
Collections.fill(list, 0);
System.out.println("填充后:" + list); // [0, 0, 0]
// 线程安全的集合
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
}
}
集合框架在 JavaWeb 中的应用
在 JavaWeb 开发中,集合框架的应用非常广泛:
处理表单提交的批量数据:
// Servlet中获取多选框数据
String[] hobbies = request.getParameterValues("hobby");
List<String> hobbyList = Arrays.asList(hobbies); // 数组转集合
存储数据库查询结果:
// 查询所有用户
List<User> userList = new ArrayList<>();
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
userList.add(user);
}
request.setAttribute("users", userList); // 存入请求域
处理 JSON 数据:
前后端交互的 JSON 数组通常会转换为 Java 集合进行处理
总结与实践
知识点回顾
集合框架体系:
- Collection:存储单个元素(List 有序可重复,Set 无序不可重复)
- Map:存储键值对(key-value)
常用实现类:
- List:ArrayList(数组实现,查询快)、LinkedList(链表实现,增删快)
- Set:HashSet(哈希表,去重)、TreeSet(红黑树,排序)
- Map:HashMap(哈希表,常用)、TreeMap(排序)、LinkedHashMap(保持顺序)
泛型:定义集合时指定元素类型,确保类型安全
Collections 工具类:提供排序、查找等集合操作方法
实践任务
学生成绩管理系统:
- 创建Student类(包含姓名、学号、成绩)
- 使用ArrayList存储多个学生对象
- 实现功能:添加学生、删除学生、修改成绩、按成绩排序、查询平均分
- 使用HashMap统计各分数段的学生数量(如 90-100 分、80-89 分等)
去重与排序练习:
- 定义一个字符串数组:{"apple", "banana", "apple", "orange", "banana"}
- 使用HashSet对数组去重
- 将去重后的结果转换为TreeSet,实现按字母顺序排序
- 输出最终结果
思考:在处理大量数据时,ArrayList和LinkedList哪个性能更好?为什么?
评论前必须登录!
注册