Java 中的 Stream 与 List:深入解析与实践
简介
在 Java 编程中,Stream 和 List 是两个极为重要的概念。List 是常用的集合接口,用于存储有序且可重复的元素。而 Stream 是 Java 8 引入的新特性,它为处理集合数据提供了一种函数式、高效且简洁的方式。掌握 Stream 和 List 的使用对于编写高质量、易维护的 Java 代码至关重要。本文将深入探讨它们的基础概念、使用方法、常见实践及最佳实践。
目录
- Stream 与 List 的基础概念
- Stream 与 List 的使用方法
- 常见实践
- 最佳实践
- 小结
- 参考资料
1. Stream 与 List 的基础概念
1.1 List
List 是 Java 集合框架中的一个接口,它继承自 Collection 接口。常见的实现类有 ArrayList 和 LinkedList。
- ArrayList:基于动态数组实现,它允许快速的随机访问,适合频繁读取操作。例如:
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
- LinkedList:基于双向链表实现,它在插入和删除操作上效率更高,适合频繁修改操作。例如:
List<String> linkedList = new LinkedList<>();
linkedList.add("Cherry");
linkedList.add("Date");
1.2 Stream
Stream 是 Java 8 引入的一个接口,它代表着一系列支持串行和并行聚合操作的元素。Stream 不是数据结构,不存储数据,而是对数据源(如集合、数组)进行操作。
Stream 具有以下特点: - 函数式风格:操作以函数式编程的风格进行,避免了传统的 for 循环带来的可变状态和副作用。 - 延迟执行:Stream 的操作是延迟执行的,直到终端操作被调用才会执行计算。例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
stream.filter(n -> n % 2 == 0).forEach(System.out::println);
在上述代码中,filter
操作是中间操作,forEach
是终端操作。只有当 forEach
被调用时,filter
操作才会真正执行。
2. Stream 与 List 的使用方法
2.1 创建 Stream
- 从 List 创建:可以通过
list.stream()
方法创建串行流,list.parallelStream()
方法创建并行流。例如:
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
Stream<String> serialStream = fruits.stream();
Stream<String> parallelStream = fruits.parallelStream();
- 从数组创建:使用
Arrays.stream()
方法。例如:
int[] numbers = {1, 2, 3, 4, 5};
Stream<Integer> numberStream = Arrays.stream(numbers).boxed();
2.2 Stream 操作
Stream 操作分为中间操作和终端操作。
- 中间操作:返回一个新的 Stream,可以链式调用多个中间操作。常见的中间操作有 filter
、map
、distinct
、sorted
等。
- filter
:用于过滤元素。例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(n -> n > 3)
.forEach(System.out::println);
- `map`:用于转换元素。例如:
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
fruits.stream()
.map(String::length)
.forEach(System.out::println);
- 终端操作:执行 Stream 的计算,并返回结果或副作用。常见的终端操作有
forEach
、collect
、reduce
、count
等。forEach
:对 Stream 中的每个元素执行给定的操作。例如:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println);
- `collect`:将 Stream 中的元素收集到一个集合中。例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
2.3 List 操作
- 添加元素:使用
add
方法。例如:
List<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Blue");
- 删除元素:使用
remove
方法。例如:
colors.remove("Red");
- 获取元素:使用
get
方法。例如:
String color = colors.get(0);
3. 常见实践
3.1 数据过滤与筛选
从一个包含学生成绩的 List 中筛选出成绩大于 80 分的学生。
class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public int getScore() {
return score;
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 85),
new Student("Bob", 70),
new Student("Charlie", 90)
);
List<Student> highScorers = students.stream()
.filter(student -> student.getScore() > 80)
.collect(Collectors.toList());
highScorers.forEach(student -> System.out.println(student.getName() + ": " + student.getScore()));
}
}
3.2 数据转换与映射
将一个包含字符串的 List 中的每个字符串转换为大写形式。
List<String> words = Arrays.asList("hello", "world", "java");
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
upperCaseWords.forEach(System.out::println);
3.3 数据聚合与统计
计算一个包含数字的 List 中的总和、平均值、最大值和最小值。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Max: " + stats.getMax());
System.out.println("Min: " + stats.getMin());
4. 最佳实践
4.1 合理使用并行流
并行流可以利用多核处理器提高计算效率,但并非所有场景都适合。在数据量较大且计算任务较重时,使用并行流效果较好。例如:
List<Integer> largeNumbers = IntStream.range(1, 1000000)
.boxed()
.collect(Collectors.toList());
long startTime = System.currentTimeMillis();
largeNumbers.parallelStream()
.filter(n -> n % 2 == 0)
.count();
long endTime = System.currentTimeMillis();
System.out.println("Parallel time: " + (endTime - startTime) + " ms");
startTime = System.currentTimeMillis();
largeNumbers.stream()
.filter(n -> n % 2 == 0)
.count();
endTime = System.currentTimeMillis();
System.out.println("Serial time: " + (endTime - startTime) + " ms");
但在数据量较小或存在共享资源访问时,并行流可能会带来额外的开销,此时应使用串行流。
4.2 避免过度链式调用
虽然 Stream 支持链式调用,但过长的链式调用会使代码难以阅读和维护。可以适当拆分链式调用,增加中间变量来提高代码的可读性。例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> filteredStream = numbers.stream().filter(n -> n > 2);
Stream<Integer> mappedStream = filteredStream.map(n -> n * 2);
List<Integer> result = mappedStream.collect(Collectors.toList());
4.3 及时释放资源
如果 Stream 的数据源是文件等需要释放资源的对象,应确保在使用完后及时释放资源。可以使用 try-with-resources
语句。例如:
try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
小结
本文详细介绍了 Java 中的 Stream 和 List 的基础概念、使用方法、常见实践及最佳实践。List 是存储数据的常用集合接口,而 Stream 为处理集合数据提供了强大的函数式编程支持。通过合理使用 Stream 和 List 的各种操作,可以编写出高效、简洁且易维护的 Java 代码。
参考资料
- Oracle Java Documentation
- 《Effective Java》Third Edition
- Baeldung - Java Stream Tutorial