首页 > 编程语言 >【Java 基础篇】Java Stream 流详解

【Java 基础篇】Java Stream 流详解

时间:2024-08-24 10:48:02浏览次数:7  
标签:Java Stream stream List 元素 流中 详解 操作

原文地址:https://blog.51cto.com/techfanyi/7716839

Java Stream(流)是Java 8引入的一个强大的新特性,用于处理集合数据。它提供了一种更简洁、更灵活的方式来操作数据,可以大大提高代码的可读性和可维护性。本文将详细介绍Java Stream流的概念、用法和一些常见操作。

什么是Stream流?
在开始介绍Java Stream流之前,让我们先了解一下什么是流。流是一系列元素的序列,它可以在一次遍历的过程中逐个处理这些元素。在Java中,流是对数据的抽象,可以操作各种不同类型的数据源,如集合、数组、文件等。

Stream流的主要特点包括:

链式调用:可以通过一系列的方法调用来定义对流的操作,使代码更具可读性。
惰性求值:流上的操作不会立即执行,只有在遇到终端操作时才会触发计算。
函数式编程:流操作使用了函数式编程的思想,可以通过Lambda表达式来定义操作。
并行处理:可以轻松地将流操作并行化,充分利用多核处理器的性能。
创建Stream流
在使用Java Stream流之前,首先需要创建一个流。流可以从各种数据源中创建,包括集合、数组、文件等。

从集合创建流
可以使用集合的stream()方法来创建一个流。例如:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Stream<String> stream = names.stream();
1.
2.
从数组创建流
可以使用Arrays.stream()方法来从数组中创建一个流。例如:

int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
1.
2.
从文件创建流
可以使用Files.lines()方法来从文件中创建一个流。例如:

try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
// 处理文件中的每一行数据
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
1.
2.
3.
4.
5.
6.
流的操作
一旦创建了流,就可以对其进行各种操作。流的操作可以分为两类:中间操作和终端操作。

中间操作
中间操作是对流的一系列处理步骤,这些步骤会返回一个新的流,允许链式调用。中间操作通常用于对数据进行过滤、映射、排序等操作。一些常见的中间操作包括:

filter(Predicate<T> predicate):根据条件过滤元素。
map(Function<T, R> mapper):将元素映射为新的值。
sorted():对元素进行排序。
distinct():去重,去除重复的元素。
limit(long maxSize):限制流中元素的数量。
skip(long n):跳过流中的前n个元素。
例如,以下代码将对一个整数集合进行筛选、映射和排序操作:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.map(n -> n * 2) // 映射为原来的2倍
.sorted() // 排序
.collect(Collectors.toList()); // 收集结果
1.
2.
3.
4.
5.
6.
终端操作
终端操作是流的最后一步操作,它会触发对流的计算并产生一个最终的结果。终端操作通常包括:

forEach(Consumer<T> action):对流中的每个元素执行操作。
collect(Collector<T, A, R> collector):将流中的元素收集到一个容器中。
toArray():将流中的元素收集到数组中。
reduce(identity, accumulator):对流中的元素进行归约操作,返回一个值。
count():返回流中元素的数量。
min(comparator):返回流中的最小元素。
max(comparator):返回流中的最大元素。
allMatch(predicate):检查流中的所有元素是否都满足条件。
anyMatch(predicate):检查流中是否存在满足条件的元素。
noneMatch(predicate):检查流中是否没有元素满足条件。
findFirst():返回流中的第一个元素。
findAny():返回流中的任意一个元素。
终端操作是流的最后一步,一旦调用终端操作,流将被消耗,不能再被复用。

示例:从集合中筛选特定条件的元素
让我们通过一个示例来演示Java Stream流的使用。假设我们有一个包含学生对象的集合,每个学生对象都有姓名、年龄和成绩属性。我们想从集合中筛选出年龄大于18岁且成绩优秀的学生。

class Student {
private String name;
private int age;
private double score;

public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public double getScore() {
return score;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}

public class Main {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 20, 90.0),
new Student("Bob", 22, 85.5),
new Student("Charlie", 19, 88.5),
new Student("David", 21, 92.0),
new Student("Eva", 18, 94.5)
);

List<Student> result = students.stream()
.filter(student -> student.getAge() > 18 && student.getScore() >= 90.0)
.collect(Collectors.toList());

result.forEach(System.out::println);
}
}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
运行以上代码,将输出符合条件的学生信息:

Student{name='Alice', age=20, score=90.0}
Student{name='David', age=21, score=92.0}
1.
2.
并行流
Java Stream还提供了并行流的支持,可以充分利用多核处理器的性能。只需将普通流转换为并行流,即可实现并行化处理。

List<Student> result = students.parallelStream()
.filter(student -> student.getAge() > 18 && student.getScore() >= 90.0)
.collect(Collectors.toList());
1.
2.
3.
需要注意的是,并行流在某些情况下可能会引发线程安全问题,因此在处理共享状态时要格外小心。

更多操作
当使用Java Stream流进行数据处理时,除了基本的过滤、映射、排序和归约等操作外,还有许多其他有用的中间操作和终端操作。在本节中,我将介绍一些常见的Stream流操作,帮助你更好地理解如何使用它们。

中间操作
1. distinct()
distinct()方法用于去除流中的重复元素,返回一个去重后的新流。

示例:

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());

System.out.println(distinctNumbers); // 输出: [1, 2, 3, 4, 5]
1.
2.
3.
4.
5.
6.
2. limit(n)
limit(n)方法用于截取流中的前n个元素,返回一个包含前n个元素的新流。

示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> limitedNumbers = numbers.stream()
.limit(5)
.collect(Collectors.toList());

System.out.println(limitedNumbers); // 输出: [1, 2, 3, 4, 5]
1.
2.
3.
4.
5.
6.
3. skip(n)
skip(n)方法用于跳过流中的前n个元素,返回一个跳过前n个元素后的新流。

示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> skippedNumbers = numbers.stream()
.skip(5)
.collect(Collectors.toList());

System.out.println(skippedNumbers); // 输出: [6, 7, 8, 9, 10]
1.
2.
3.
4.
5.
6.
4. flatMap()
flatMap()方法用于将流中的每个元素映射成一个新的流,然后将这些新流合并成一个流。通常用于将嵌套的集合扁平化。

示例:

List<List<Integer>> nestedLists = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);

List<Integer> flattenedList = nestedLists.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());

System.out.println(flattenedList); // 输出: [1, 2, 3, 4, 5, 6]
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
终端操作
1. forEach()
forEach()方法用于对流中的每个元素执行指定的操作。

示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(name -> System.out.println("Hello, " + name));
1.
2.
3.
2. toArray()
toArray()方法用于将流中的元素收集到数组中。

示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer[] numberArray = numbers.stream()
.toArray(Integer[]::new);
1.
2.
3.
3. reduce(identity, accumulator)
reduce()方法用于对流中的元素进行归约操作,返回一个值。identity是初始值,accumulator是归约函数。

示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);

System.out.println(sum); // 输出: 15
1.
2.
3.
4.
5.
4. collect()
collect()方法用于将流中的元素收集到一个集合或其他数据结构中。可以使用Collectors类提供的各种工厂方法创建不同类型的集合。

示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> collectedNames = names.stream()
.collect(Collectors.toList());

Set<String> collectedSet = names.stream()
.collect(Collectors.toSet());

Map<String, Integer> collectedMap = names.stream()
.collect(Collectors.toMap(name -> name, String::length));
1.
2.
3.
4.
5.
6.
7.
8.
9.
5. min(comparator) 和 max(comparator)
min(comparator)和max(comparator)方法用于查找流中的最小和最大元素,需要传入一个比较器(Comparator)来定义比较规则。

示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> minNumber = numbers.stream()
.min(Integer::compareTo);

Optional<Integer> maxNumber = numbers.stream()
.max(Integer::compareTo);

System.out.println(minNumber.orElse(0)); // 输出: 1
System.out.println(maxNumber.orElse(0)); // 输出: 5
1.
2.
3.
4.
5.
6.
7.
8.
9.
6. anyMatch(predicate)、allMatch(predicate) 和 noneMatch(predicate)
这些方法用于检查流中的元素是否满足给定的条件。

anyMatch(predicate):检查流中是否有任意一个元素满足条件。
allMatch(predicate):检查流中的所有元素是否都满足条件。
noneMatch(predicate):检查流中是否没有元素满足条件。
示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean anyGreaterThanThree = numbers.stream()
.anyMatch(n -> n > 3);

boolean allGreaterThanThree = numbers.stream()
.allMatch(n -> n > 3);

boolean noneGreaterThanTen = numbers.stream()
.noneMatch(n -> n > 10);

System.out.println(anyGreaterThanThree); // 输出: true
System.out.println(allGreaterThanThree); // 输出: false
System.out.println(noneGreaterThanTen); // 输出: true
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
7. findFirst() 和 findAny()
findFirst()方法返回流中的第一个元素(在串行流中通常是第一个元素,但在并行流中不确定),findAny()方法返回流中的任意一个元素。

示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> first = names.stream()
.findFirst();

Optional<String> any = names.parallelStream()
.findAny();
1.
2.
3.
4.
5.
6.
这些只是Java Stream流的一些常见操作,Stream API提供了更多的方法来处理数据。根据具体的需求,你可以组合这些操作来构建复杂的数据处理流程。希望这些示例能帮助你更好地理解和使用Java Stream流。

注意事项
在使用Java Stream流时,有一些注意事项需要考虑,以确保代码的正确性和性能。以下是一些常见的注意事项:

不可重用性: 一旦创建了一个Stream对象并执行了终端操作,该Stream就不能再被重用。如果需要对同一数据集进行多次处理,应该每次都创建新的Stream对象。
惰性求值: Stream是惰性求值的,中间操作只会在终端操作触发后才会执行。这意味着中间操作不会立即产生结果,而是在需要结果时才进行计算。这可以帮助节省计算资源,但也需要谨慎处理,以免产生意外的行为。
并行流的线程安全性: 如果使用并行流(parallelStream()),要确保Stream操作是线程安全的。一些操作可能会引发并发问题,需要适当的同步或避免使用并行流。
流的关闭: 如果你使用的是基于IO的流(如Files.lines()),需要确保在使用完后关闭流,以释放资源。
性能注意事项: Stream操作的性能可能会受到数据量的影响。在大数据集上使用Stream时,要注意性能问题,可以考虑使用并行流或其他优化方法。
空值处理: 在使用Stream时,要注意空值(null)的处理,避免空指针异常。可以使用filter、map等操作来过滤或转换空值。
有状态操作: 一些Stream操作是有状态的,例如sorted和distinct,它们可能需要缓存所有元素,因此在处理大数据集时要谨慎使用,以免导致内存溢出。
自定义收集器: 如果需要自定义收集器(Collector),要确保它的线程安全性和正确性,以便在Stream中使用。
不可变性: 推荐使用不可变对象和不可变集合来处理Stream,以避免并发问题。
了解Stream操作的复杂度: 不同的Stream操作具有不同的时间复杂度。了解操作的复杂度有助于选择最适合的操作来满足性能需求。
总之,使用Java Stream流可以编写更简洁和可读性强的代码,但在使用过程中需要考虑到流的惰性求值、线程安全性、性能等方面的注意事项,以确保代码的正确性和性能。

总结
Java Stream流是一项强大的特性,可以极大地简化集合数据的处理。通过中间操作和终端操作的组合,我们可以轻松地实现各种复杂的数据处理任务。同时,流还提供了并行处理的支持,可以充分利用多核处理器的性能。

要注意的是,流是一次性的,一旦调用了终端操作,流将被消耗,不能再被复用。此外,在使用并行流时要注意线程安全的问题。

希望本文能帮助你更好地理解和使用Java Stream流,提高代码的可读性和可维护性。如果你对Java Stream流还有更多的疑问或想要深入了解,可以查阅官方文档或进一步学习相关的教程和示例。 Happy coding!
-----------------------------------
©著作权归作者所有:来自51CTO博客作者繁依Fanyi的原创作品,请联系作者获取转载授权,否则将追究法律责任
【Java 基础篇】Java Stream 流详解
https://blog.51cto.com/techfanyi/7716839

标签:Java,Stream,stream,List,元素,流中,详解,操作
From: https://www.cnblogs.com/eyesfree/p/18377516

相关文章

  • Java引用类型与WeakHashMap
    Java中的引用类型强引用:直接引用,只要强引用存在就不回收软引用:描述一些非必须得对象,内存不足时可能会回收弱引用:下一次GC扫描到就会回收虚引用:用于在对象回收后执行清理操作,与引用队列配合使用虚引用在创建时可以指定引用队列在被回收之后可以通过队列判断回收......
  • 哈夫曼树和哈夫曼编码详解(包含Java代码实现)
    目录什么是哈夫曼树?如何构造哈夫曼树?构造过程代码实现哈夫曼树的结构构建哈夫曼树并计算WPL值测试代码什么是哈夫曼编码?如何构建哈夫曼编码?构建过程代码实现什么是哈夫曼树?哈夫曼树又称为最优树,是一类带权路径长度最短的树,在实际中有着广泛的应用。介绍哈夫曼树......
  • java 查询数据库并生成多层children
    首先,定义一个表示组织结构的简单类:publicclassOrganization{privateintid;privateintparentId;privateStringname;privateList<Organization>children;//省略构造函数、getter和setter}然后,编写一个方法来查询数据库并构建多层嵌套的......
  • 【kubernetes】The LocalStreamEnvironment cannot be used when submitting
    1.概述新手上路,首先参考文章:【Flink】Mac下使用flink-kubernetes-operator本地运行flink程序在这个文章中,我们知道了如何使用demo提交flink任务。但是如果我们的机器没有kubectl命令,我们改怎么提交任务到flink呢?这里我们可以使用代码提交,此处文章参考:【kubernetes】使......
  • 在Java中常见的池化技术
    什么是池化技术池化技术的原理可以用一个生活中的比喻来理解。 想象有一个图书馆,里面有很多人需要借书和还书。如果没有任何管理措施,每次有人借书时,图书馆管理员都要去仓库找一本新的书拿出来给读者,等读者还书时,管理员又要把书放回仓库。这样的过程非常耗时耗力,而且仓库里......
  • 基于Java+Vue的采购管理系统:提高决策效率(项目代码)
        前言:采购管理系统是一个综合性的管理平台,旨在提高采购过程的效率、透明度,并优化供应商管理。以下是对各个模块的详细解释:一、供应商准入供应商注册:供应商通过在线平台进行注册,填写基本信息和资质文件。资质审核:系统对供应商提交的资质文件进行自动或人工审核,确保......
  • 009java jsp SSM springboot月度员工绩效考核管理系统绩效指标管理(源码+文档+PPT+任务
     项目技术:Springboot+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:window......
  • 015java jsp SSM springboot在线视频课程教育学习平台系统(源码+文档+PPT+开题+运行视
     项目技术:Springboot+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:window......
  • Java 12 新特性—Switch 表达式
    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!阶段1、深入多线程阶段2、深入多线程设计模式阶段3、深入juc源码解析阶段4、深入jdk其余源码解析......
  • Java 12 新特性—新增 String API
    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!阶段1、深入多线程阶段2、深入多线程设计模式阶段3、深入juc源码解析阶段4、深入jdk其余源码解析......