Java集合、高级集合与Stream流实战精讲
本文聚焦Java集合体系核心内容,从基础集合到高级集合,再到Stream流的实战应用,结合源码解析、代码案例和避坑技巧,全程干货无冗余。
一、Java集合体系概述
Java集合是用于存储多个对象(引用数据类型)的容器,替代了数组的局限性(长度固定、无法动态扩容、仅能存储相同类型基本数据/引用数据),核心接口为Collection和Map,二者构成整个集合体系的根基,与数组相比,集合支持动态扩容、便捷的增删改查操作,且能存储不同类型的引用数据(需统一为父类类型)。
1.1 集合体系结构(核心分类)
集合体系分为两大分支,无继承关系,核心区别在于存储方式不同,后续所有集合实现类均围绕这两大分支展开:
-
Collection接口:存储单个元素的集合,核心子接口为
List(有序、可重复,元素有索引)、Set(无序、不可重复,无索引),常用实现类有ArrayList、LinkedList、HashSet等; -
Map接口:存储键值对(key-value)的集合,key唯一(不可重复),value可重复,key和value均为引用数据类型,常用实现类有
HashMap、TreeMap、LinkedHashMap等。
补充:集合与数组的核心区别
| 特性 | 数组 | 集合 |
|---|---|---|
| 长度特性 | 长度固定,创建后无法修改 | 长度动态,支持自动扩容,无需手动指定长度 |
| 存储类型 | 可存储基本数据类型、引用数据类型 | 仅能存储引用数据类型(基本数据类型需装箱为包装类) |
| 操作便捷性 | 增删改查需手动实现,无内置方法 | 提供丰富的内置方法(add、remove、get等),操作便捷 |
| 适用场景 | 数据量固定、类型单一的场景 | 数据量不固定、需频繁增删改查的场景 |
二、基础集合实战(核心实现类)
基础集合主要围绕Collection接口的List和Set子接口,以及Map接口的核心实现类,是日常开发中最常用的集合,重点掌握各自的特性、底层实现和适用场景。
2.1 List接口(有序、可重复)
List接口的核心特点:元素有序(插入顺序与存储顺序一致)、可重复、有索引(可通过索引访问元素),支持通过索引进行增删改查,核心实现类为ArrayList和LinkedList,二者底层实现不同,适用场景有明显差异。
2.1.1 ArrayList(重点)
底层实现:基于动态数组(Object[])实现,初始容量为10,扩容机制为“原容量的1.5倍”(扩容时创建新数组,复制原数组元素,效率较低)。
核心特性:查询效率高(通过索引直接访问,时间复杂度O(1)),增删效率低(需移动数组元素,时间复杂度O(n)),线程不安全,适合查询频繁、增删较少的场景。
实战案例(常用方法):
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
// 1. 创建ArrayList集合(泛型指定存储类型,避免类型转换异常)
List<String> list = new ArrayList<>();
// 2. 常用方法:add(添加元素)
list.add("Java");
list.add("集合");
list.add("Stream流");
list.add("Java"); // 允许重复元素
// 3. 常用方法:get(通过索引获取元素,索引从0开始)
String element = list.get(0);
System.out.println("索引0的元素:" + element); // 输出:Java
// 4. 常用方法:set(修改指定索引的元素)
list.set(2, "高级集合");
System.out.println("修改后索引2的元素:" + list.get(2)); // 输出:高级集合
// 5. 常用方法:remove(删除元素,支持索引/元素)
list.remove(3); // 删除索引3的元素
list.remove("集合"); // 删除指定元素
// 6. 遍历集合(三种方式)
// 方式1:for循环(利用索引)
System.out.println("for循环遍历:");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
// 方式2:for-each循环
System.out.println("\nfor-each遍历:");
for (String s : list) {
System.out.print(s + " ");
}
// 方式3:迭代器(Iterator)
System.out.println("\n迭代器遍历:");
java.util.Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
2.1.2 LinkedList
底层实现:基于双向链表实现,每个节点包含前驱节点、后继节点和元素值,无数组扩容问题。
核心特性:查询效率低(需从头/尾遍历查找,时间复杂度O(n)),增删效率高(仅需修改节点的引用,时间复杂度O(1)),线程不安全,适合增删频繁、查询较少的场景(如队列、栈)。
补充:LinkedList额外实现了Deque接口,可作为队列(先进先出)、栈(先进后出)使用,常用方法有offer()(入队)、poll()(出队)、push()(入栈)、pop()(出栈)。
2.2 Set接口(无序、不可重复)
Set接口的核心特点:元素无序(插入顺序与存储顺序不一致,无索引)、不可重复(重复元素无法添加成功),核心实现类为HashSet和TreeSet,底层依赖哈希表/红黑树实现去重。
2.2.1 HashSet(重点)
底层实现:基于哈希表(HashMap的底层结构)实现,去重原理:先通过hashCode()方法计算元素的哈希值,再通过equals()方法判断元素是否相等(哈希值相同且equals返回true,视为重复元素)。
核心特性:无序、不可重复,查询/增删效率高(时间复杂度O(1)),线程不安全,存储null值(仅能存储一个null),适合无需排序、仅需去重的场景。
避坑点:存储自定义对象时,必须重写hashCode()和equals()方法,否则无法实现去重(默认使用Object类的方法,比较的是内存地址)。
实战案例(自定义对象去重):
import java.util.HashSet;
import java.util.Set;
// 自定义对象(学生类)
class Student {
private String name;
private int age;
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 重写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 age == student.age && name.equals(student.name);
}
@Override
public int hashCode() {
return java.util.Objects.hash(name, age);
}
// 重写toString(),方便打印
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
public class HashSetDemo {
public static void main(String[] args) {
Set<Student> studentSet = new HashSet<>();
// 添加元素(重复元素无法添加)
studentSet.add(new Student("张三", 20));
studentSet.add(new Student("李四", 21));
studentSet.add(new Student("张三", 20)); // 重复元素,添加失败
// 遍历Set集合(无索引,无法用for循环,可用for-each/迭代器)
System.out.println("HashSet遍历:");
for (Student student : studentSet) {
System.out.println(student);
}
}
}
2.2.2 TreeSet
底层实现:基于红黑树(平衡二叉树)实现,核心特点:有序(默认按自然排序,可自定义排序规则)、不可重复。
核心特性:有序、不可重复,查询/增删效率较高(时间复杂度O(log n)),线程不安全,不允许存储null值,适合需要排序且去重的场景。
排序方式:
-
自然排序:元素实现
Comparable接口,重写compareTo()方法,指定排序规则; -
自定义排序:创建TreeSet时,传入
Comparator接口实现类,重写compare()方法。
2.3 Map接口(键值对存储)
Map接口的核心特点:存储键值对(key-value),key唯一(不可重复),value可重复,key和value均可以为null(HashMap允许key为null,TreeMap不允许),核心实现类为HashMap和TreeMap。
2.3.1 HashMap(重点)
底层实现:JDK 1.8及以上,基于**哈希表(数组+链表+红黑树)**实现,当链表长度超过8且数组长度>=64时,链表转为红黑树(提升查询效率);数组初始容量16,扩容机制为“原容量的2倍”。
核心特性:无序(key的存储顺序与插入顺序不一致)、key唯一、value可重复,查询/增删效率高(时间复杂度O(1)),线程不安全,适合无需排序、键值对存储的场景(日常开发使用最多)。
实战案例(常用方法):
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapDemo {
public static void main(String[] args) {
// 1. 创建HashMap集合(泛型指定key和value的类型)
Map<String, Integer> map = new HashMap<>();
// 2. 常用方法:put(添加键值对,key重复时覆盖value)
map.put("Java", 100);
map.put("集合", 90);
map.put("Stream流", 85);
map.put("Java", 95); // key重复,覆盖原value
// 3. 常用方法:get(通过key获取value,key不存在返回null)
Integer score = map.get("集合");
System.out.println("集合的分数:" + score); // 输出:90
// 4. 常用方法:remove(通过key删除键值对)
map.remove("Stream流");
// 5. 遍历Map集合(三种方式)
// 方式1:遍历key(keySet())
System.out.println("遍历key:");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println("key:" + key + ",value:" + map.get(key));
}
// 方式2:遍历键值对(entrySet(),推荐,效率更高)
System.out.println("遍历键值对:");
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
for (Map.Entry<String, Integer> entry : entrySet) {
System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
}
// 方式3:遍历value(values())
System.out.println("遍历value:");
for (Integer value : map.values()) {
System.out.print(value + " ");
}
}
}
2.3.2 TreeMap
底层实现:基于红黑树实现,核心特点:key有序(默认按自然排序,可自定义排序规则)、key唯一,不允许key为null。
核心特性:有序、key唯一、value可重复,查询/增删效率较高(时间复杂度O(log n)),线程不安全,适合需要按key排序的键值对存储场景。
三、高级集合(JDK 1.8+新特性)
高级集合是JDK 1.8及以上新增的集合类,基于基础集合优化,解决基础集合的线程安全、性能瓶颈等问题,核心包括ConcurrentHashMap(线程安全的HashMap)、CopyOnWriteArrayList(线程安全的ArrayList)、CopyOnWriteArraySet(线程安全的Set)。
3.1 ConcurrentHashMap(重点)
核心作用:替代传统的Hashtable(线程安全但效率低,全局锁),实现线程安全的同时,提升并发效率,底层采用“分段锁”(JDK 1.7)、“CAS+ synchronized”(JDK 1.8)实现。
核心特性:线程安全、无序、key唯一、value可重复,并发效率高(仅锁定当前操作的节点,而非全局),支持高并发场景,允许key为null。
实战案例(并发场景使用):
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
// 创建线程安全的ConcurrentHashMap
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
// 多线程环境下添加键值对(无需额外加锁)
new Thread(() -> {
for (int i = 0; i < 5; i++) {
concurrentMap.put("key" + i, "value" + i);
}
}).start();
new Thread(() -> {
for (int i = 5; i < 10; i++) {
concurrentMap.put("key" + i, "value" + i);
}
}).start();
// 等待线程执行完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 遍历集合
System.out.println("ConcurrentHashMap遍历:");
for (Map.Entry<String, String> entry : concurrentMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
3.2 CopyOnWriteArrayList
核心作用:线程安全的ArrayList,底层采用“写时复制”机制(写入数据时,复制一份原数组,修改复制后的数组,修改完成后替换原数组),读操作无需加锁,写操作加锁。
核心特性:线程安全、有序、可重复,读效率高(无锁),写效率低(需复制数组),适合读多写少的并发场景。
3.3 CopyOnWriteArraySet
核心作用:线程安全的Set,底层依赖CopyOnWriteArrayList实现,本质是“封装了CopyOnWriteArrayList的Set”,实现去重功能。
核心特性:线程安全、无序、不可重复,读效率高,写效率低,适合读多写少、需要去重的并发场景。
四、Stream流(JDK 1.8+核心新特性)
Stream流是JDK 1.8新增的流式编程工具,用于简化集合、数组的遍历、过滤、映射、排序等操作,核心思想是“面向操作,而非面向过程”,无需手动编写循环,代码更简洁、优雅,且支持链式调用。
核心特点:不存储数据、不修改原数据(操作后返回新的Stream)、支持链式调用、可并行执行(提升效率)。
4.1 Stream流的获取方式
Stream流的获取主要有3种方式,重点掌握集合和数组的获取方式:
-
集合获取:通过
Collection接口的stream()方法(串行流)、parallelStream()方法(并行流); -
数组获取:通过
Arrays.stream(数组)方法,或Stream.of(数组)方法; -
直接创建:通过
Stream.of(元素1, 元素2, ...)方法,直接创建Stream流。
实战案例(获取Stream流):
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamGetDemo {
public static void main(String[] args) {
// 1. 集合获取Stream流(串行流+并行流)
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Stream");
list.add("集合");
Stream<String> stream1 = list.stream(); // 串行流
Stream<String> stream2 = list.parallelStream(); // 并行流
// 2. 数组获取Stream流
String[] arr = {"a", "b", "c", "d"};
Stream<String> stream3 = Arrays.stream(arr);
Stream<String> stream4 = Stream.of(arr);
// 3. 直接创建Stream流
Stream<Integer> stream5 = Stream.of(1, 2, 3, 4, 5);
}
}
4.2 Stream流的核心操作(重点)
Stream流的操作分为两大类:中间操作(返回Stream流,支持链式调用)、终止操作(返回非Stream流,结束链式调用),必须有终止操作,中间操作才会执行(延迟执行)。
4.2.1 中间操作
常用中间操作:过滤、映射、排序、去重,重点掌握前4种:
-
过滤(filter):根据条件筛选元素,保留满足条件的元素;
-
映射(map):将Stream流中的元素映射为另一种类型(如将String转为Integer);
-
排序(sorted):对元素进行排序(自然排序/自定义排序);
-
去重(distinct):去除Stream流中的重复元素(依赖equals()方法);
-
限制(limit):限制Stream流的元素个数;
-
跳过(skip):跳过指定个数的元素。
4.2.2 终止操作
常用终止操作:遍历、收集、统计、判断,重点掌握前3种:
-
遍历(forEach):遍历Stream流中的所有元素;
-
收集(collect):将Stream流转换为集合(List/Set/Map)或数组;
-
统计(count):统计Stream流中的元素个数;
-
判断(anyMatch/allMatch/noneMatch):判断元素是否满足指定条件。
4.2.3 Stream流实战案例(链式调用)
结合中间操作和终止操作,实现复杂的数据处理,代码简洁高效:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamOperateDemo {
public static void main(String[] args) {
// 准备数据:List集合存储学生信息(姓名、年龄、成绩)
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("张三", 20, 85));
studentList.add(new Student("李四", 21, 92));
studentList.add(new Student("王五", 19, 78));
studentList.add(new Student("赵六", 20, 90));
studentList.add(new Student("张三", 20, 85)); // 重复元素
// 需求:筛选出年龄>=20、成绩>=85的学生,去重后按成绩降序排序,最后收集为List集合
List<Student> resultList = studentList.stream()
.filter(student -> student.getAge() >= 20) // 过滤:年龄>=20
.filter(student -> student.getScore() >= 85) // 过滤:成绩>=85
.distinct() // 去重(依赖重写的equals和hashCode)
.sorted((s1, s2) -> s2.getScore() - s1.getScore()) // 按成绩降序排序
.collect(Collectors.toList()); // 收集为List集合
// 遍历结果
System.out.println("筛选后的学生:");
resultList.forEach(System.out::println); // 方法引用,简化遍历
// 额外需求:统计筛选后学生的个数
long count = studentList.stream()
.filter(student -> student.getAge() >= 20)
.filter(student -> student.getScore() >= 85)
.distinct()
.count();
System.out.println("筛选后学生个数:" + count);
// 额外需求:将学生姓名收集为Set集合(去重)
List<String> nameList = studentList.stream()
.map(Student::getName) // 映射:将Student转为String(姓名)
.distinct()
.collect(Collectors.toList());
System.out.println("学生姓名(去重):" + nameList);
}
}
// 学生类(重写equals、hashCode、toString)
class Student {
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
// getter方法(用于Stream流中获取属性)
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getScore() {
return score;
}
// 重写equals、hashCode(用于distinct去重)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && score == student.score && name.equals(student.name);
}
@Override
public int hashCode() {
return java.util.Objects.hash(name, age, score);
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", score=" + score + "}";
}
}
4.3 Stream流的并行流(parallelStream)
并行流是Stream流的并行执行方式,底层依赖线程池(ForkJoinPool),可充分利用CPU多核资源,提升大数据量处理的效率,与串行流的区别在于“执行方式”,API完全一致。
注意点:并行流适合大数据量处理,小数据量场景下,串行流效率更高(避免线程切换的开销);并行流中,若操作有状态(如排序、去重),需注意线程安全。
实战案例(并行流使用):
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ParallelStreamDemo {
public static void main(String[] args) {
// 准备大数据量(10000个元素)
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(i);
}
// 并行流处理:筛选出偶数,平方后收集为List
long start = System.currentTimeMillis();
List<Integer> evenList = list.parallelStream()
.filter(num -> num % 2 == 0)
.map(num -> num * num)
.collect(Collectors.toList());
long end = System.currentTimeMillis();
System.out.println("并行流处理时间:" + (end - start) + "ms");
System.out.println("筛选出的偶数个数:" + evenList.size());
}
}
五、集合与Stream流实战综合案例
结合本期所有知识点,实现“学生成绩管理系统”进阶版,使用集合存储学生信息,通过Stream流实现数据筛选、排序、统计等功能,巩固核心知识点。
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StudentScoreSystem {
public static void main(String[] args) {
// 1. 存储学生信息(使用ArrayList集合)
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("张三", 20, "计算机", 85));
studentList.add(new Student("李四", 21, "计算机", 92));
studentList.add(new Student("王五", 19, "数学", 78));
studentList.add(new Student("赵六", 20, "数学", 90));
studentList.add(new Student("孙七", 22, "计算机", 88));
studentList.add(new Student("周八", 20, "英语", 82));
studentList.add(new Student("吴九", 21, "英语", 75));
studentList.add(new Student("郑十", 19, "计算机", 95));
// 需求1:筛选出计算机专业、成绩>=85的学生,按成绩降序排序,输出姓名和成绩
System.out.println("=== 计算机专业成绩>=85的学生 ===");
studentList.stream()
.filter(student -> "计算机".equals(student.getMajor()))
.filter(student -> student.getScore() >= 85)
.sorted((s1, s2) -> s2.getScore() - s1.getScore())
.forEach(student -> System.out.println("姓名:" + student.getName() + ",成绩:" + student.getScore()));
// 需求2:按专业分组,统计每个专业的学生个数和平均成绩(存储为Map)
System.out.println("\n=== 按专业分组统计 ===");
Map<String, Map<String, Object>> groupMap = studentList.stream()
.collect(Collectors.groupingBy(
Student::getMajor, // 分组依据:专业
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
// 统计个数
long count = list.size();
// 统计平均成绩
double avgScore = list.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
// 封装为Map
Map<String, Object> infoMap = new java.util.HashMap<>();
infoMap.put("count", count);
infoMap.put("avgScore", String.format("%.1f", avgScore));
return infoMap;
}
)
));
// 遍历分组统计结果
groupMap.forEach((major, info) -> {
System.out.println("专业:" + major + ",学生个数:" + info.get("count") + ",平均成绩:" + info.get("avgScore"));
});
// 需求3:找出所有学生中的最高分、最低分,以及对应的学生姓名
System.out.println("\n=== 成绩极值统计 ===");
// 最高分
Student maxScoreStudent = studentList.stream()
.max((s1, s2) -> s1.getScore() - s2.getScore())
.orElse(null);
// 最低分
Student minScoreStudent = studentList.stream()
.min((s1, s2) -> s1.getScore() - s2.getScore())
.orElse(null);
System.out.println("最高分:" + maxScoreStudent.getScore() + ",姓名:" + maxScoreStudent.getName());
System.out.println("最低分:" + minScoreStudent.getScore() + ",姓名:" + minScoreStudent.getName());
}
}
// 学生类(完善属性和方法)
class Student {
private String name;
private int age;
private String major; // 专业
private int score;
public Student(String name, int age, String major, int score) {
this.name = name;
this.age = age;
this.major = major;
this.score = score;
}
// getter方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getMajor() {
return major;
}
public int getScore() {
return score;
}
// 重写toString
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", major='" + major + "', score=" + score + "}";
}
}
六、核心避坑点与学习技巧
6.1 集合避坑点
-
ArrayList增删效率低,避免在频繁增删的场景使用(优先用LinkedList);
-
HashSet存储自定义对象时,必须重写hashCode()和equals()方法,否则无法去重;
-
HashMap线程不安全,并发场景下优先用ConcurrentHashMap,而非Hashtable;
-
TreeMap不允许key为null,HashMap允许key为null(仅一个);
-
集合遍历中,不能直接删除元素(会报ConcurrentModificationException),需用迭代器或Stream流删除。
6.2 Stream流避坑点
-
Stream流是一次性的,执行终止操作后,无法再次使用(需重新获取Stream流);
-
中间操作是延迟执行的,没有终止操作,中间操作不会执行;
-
并行流适合大数据量,小数据量场景下串行流效率更高;
-
Stream流不修改原集合/数组,所有操作都是基于副本,返回新的结果。
6.3 AI辅助学习技巧
-
遇到集合底层原理(如HashMap红黑树转换、ConcurrentHashMap锁机制),可让AI结合源码讲解,快速理解;
-
Stream流复杂操作(如分组统计、多条件筛选),可让AI生成案例,对比自己的代码优化;
-
排查集合/Stream流报错(如ConcurrentModificationException),复制报错信息和代码,让AI定位问题并修改。
七、知识点总结
-
集合体系:核心分为Collection(单个元素)和Map(键值对),List有序可重复,Set无序不可重复,Map键唯一值可重复;
-
基础集合:ArrayList(查询优)、LinkedList(增删优)、HashSet(去重无序)、HashMap(键值对无序),是日常开发核心;
-
高级集合:ConcurrentHashMap、CopyOnWriteArrayList等,解决基础集合的线程安全问题,适合并发场景;
-
Stream流:JDK 1.8+新特性,简化集合/数组操作,支持链式调用,分为中间操作和终止操作,可并行执行;
-
核心重点:掌握各集合的底层实现、适用场景,以及Stream流的核心操作,能结合实战场景灵活运用。
本文衔接前文Java基础知识点,后续将讲解Java异常处理、多线程等进阶内容,持续夯实Java编程能力,所有代码均可直接复制运行,便于实战练习。 Java集合、高级集合与Stream流实战精讲
本文聚焦Java集合体系核心内容,从基础集合到高级集合,再到Stream流的实战应用,结合源码解析、代码