在Java编程中,流(Stream)是指可以进行顺序或并行处理的元素序列。
一个流可以包含多个中间操作,最终以一个终端操作结束,并返回一个结果。
什么是流?
流的概念是通过Java 8引入的Stream API进行管理的。
可以将流想象成一个生产线,其中物品需要被制造、分类,然后包装以便运输。 在Java中,这些物品可以是对象或者对象的集合,操作则是制造、分类和包装,而生产线本身就是流。
流的组成部分主要包括:
- 初始输入
- 中间操作
- 终端操作
- 最终结果
让我们来探讨一下Java流的一些特性:
- 流并非内存中的数据结构,而是一系列数组、对象或对象集合,通过特定方法进行操作。
- 流本质上是声明式的,即您指定要做什么,而不是如何去做。
- 流只能被消费一次,因为它们不存储任何数据。
- 流不会修改原始数据结构,它只是从中派生出新的结构。
- 它返回通过管道中最终方法得出的最终结果。
流API与集合处理
集合是一种内存中的数据结构,用于存储和操作数据。集合提供了诸如Set、Map、List等数据结构来存储数据。而流,则是一种在数据通过管道处理后有效传输数据的方式。
以下是一个ArrayList集合的示例:
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(0, 3); System.out.println(list); } } Output: [3]
如上述示例所示,您可以创建一个ArrayList集合,在其中存储数据,并使用各种方法对这些数据进行操作。
通过使用流,您可以对现有的数据结构进行操作,并返回新的、经过修改的值。以下示例展示了如何创建一个ArrayList集合并使用流进行过滤。
import java.util.ArrayList; import java.util.stream.Stream; public class Main { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList(); for (int i = 0; i < 20; i++) { list.add(i+1); } System.out.println(list); Stream<Integer> filtered = list.stream().filter(num -> num > 10); filtered.forEach(num -> System.out.println(num + " ")); } } #Output [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] 11 12 13 14 15 16 17 18 19 20
在以上示例中,我们使用已存在的列表创建了一个流,并遍历该列表以过滤出所有大于10的值。需要注意的是,流并不存储任何内容,它只是遍历列表并输出结果。如果您尝试打印流本身,您只会获得一个流的引用,而不是具体的值。
使用 Java Stream API
Java Stream API接收一个元素源集合或元素序列,然后对它们执行各种操作以获得最终结果。流可以被视为一个管道,一系列元素通过该管道并被以某种方式转换。
流可以从多种源创建,包括:
- 集合,例如List或Set。
- 数组。
- 使用缓冲区的文件及其路径。
流中的操作分为两种类型:
- 中间操作
- 终端操作
中间与终端操作
每个中间操作都会返回一个新的流,这个新流使用指定的方法转换输入。实际上,它不会立刻遍历任何元素,而是将转换传递给下一个流。只有在执行终端操作时,流才会被遍历以获得最终结果。
例如,如果有一个包含10个数字的列表,您希望过滤这些数字,然后将其映射到其他值。并不会立即遍历列表中的每个元素来获取过滤结果并进行映射。相反,只会检查单个元素,如果元素满足条件,它才会被映射,形成一个包含新元素的流。
映射操作只会在满足过滤条件的单个元素上执行,而不是在整个列表上。在终端操作阶段,这些结果会被遍历并组合成最终的输出。
一旦执行了终端操作,流就会被消耗,不能再次使用。如果需要再次执行相同操作,必须创建一个新的流。
资料来源:无聊的开发
我们现在对流的工作原理有了初步了解,接下来深入探讨Java中流的实现细节。
#1. 空流
可以使用Stream API的`empty()`方法创建一个空流。
import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream emptyStream = Stream.empty(); System.out.println(emptyStream.count()); } } Output: 0
如果您打印这个流中的元素数量,您会得到0作为输出,因为它是一个不包含任何元素的空流。空流在避免空指针异常时非常有用。
#2. 从集合流
诸如List和Set之类的集合类提供了一个`stream()`方法,允许您从集合中创建流。然后,您可以遍历创建的流以获取最终结果。
ArrayList<Integer> list = new ArrayList(); for (int i = 0; i < 20; i++) { list.add(i+1); } System.out.println(list); Stream<Integer> filtered = list.stream().filter(num -> num > 10); filtered.forEach(num -> System.out.println(num + " ")); #Output [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] 11 12 13 14 15 16 17 18 19 20
#3. 从数组流
可以使用`Arrays.stream()`方法从数组中创建流。
import java.util.Arrays; public class Main { public static void main(String[] args) { String[] stringArray = new String[]{"this", "is", "techblik.com"}; Arrays.stream(stringArray).forEach(item -> System.out.print(item + " ")); } } #Output this is techblik.com
您还可以指定创建流的元素的起始和结束索引。起始索引是包含的,而结束索引是排除的。
String[] stringArray = new String[]{"this", "is", "techblik.com"}; Arrays.stream(stringArray, 1, 3).forEach(item -> System.out.print(item + " ")); Output: is techblik.com
#4. 使用流查找最小和最大数字
可以使用Java中的比较器来查找集合或数组中的最大和最小数字。`min()`和`max()`方法接受一个比较器,并返回一个Optional对象。
Optional对象是一个容器对象,可能包含或不包含非空值。如果它包含非空值,则调用其`get()`方法会返回该值。
import java.util.Arrays; import java.util.Optional; public class MinMax { public static void main(String[] args) { Integer[] numbers = new Integer[]{21, 82, 41, 9, 62, 3, 11}; Optional<Integer> maxValue = Arrays.stream(numbers).max(Integer::compare); System.out.println(maxValue.get()); Optional<Integer> minValue = Arrays.stream(numbers).min(Integer::compare); System.out.println(minValue.get()); } } #Output 82 3
学习资源
现在您已经对Java中的流有了基本的了解,这里有5个资源可以帮助您掌握Java 8:
#1. Java 8实战
本书是一本介绍Java 8新特性的指南,包括流、lambda表达式和函数式编程。书中还包含测验和知识检查题,以帮助您巩固所学知识。
您可以在亚马逊上购买本书的平装本和有声读物版本。
#2. Java 8 Lambdas:面向大众的函数式编程
本书主要讲解Lambda表达式如何影响Java语言,尤其针对核心Java SE开发人员。书中包含了清晰的解释、代码练习和示例,帮助您掌握Java 8的lambda表达式。
亚马逊上有平装版和Kindle版本。
#3. Java SE 8为真正没有耐心的人准备
如果您是一位经验丰富的Java SE开发人员,本书将指导您了解Java SE 8中的改进,包括流API、lambda表达式的引入、Java并发编程的改进,以及一些人们不熟悉的Java 7功能。
本书仅在亚马逊上提供平装版。
#4. 使用Lambdas和Streams学习Java函数式编程
Udemy上的这门课程探讨了Java 8和9中函数式编程的基础知识。Lambda表达式、方法引用、流和函数式接口是本课程的重点。
本课程还包含许多与函数式编程相关的Java难题和练习。
#5. Java类库
Java类库是Coursera提供的核心Java专业化课程的一部分。它将教您如何使用Java泛型编写类型安全的代码、了解由4000多个类组成的类库、如何使用文件以及处理运行时错误。但是,参加本课程需要一些先决条件:
- Java入门
- Java面向对象编程简介
- Java中的面向对象层次结构
最后的话
Java Stream API和Java 8中引入的Lambda函数简化并改进了Java中的许多方面,例如并行迭代、函数式接口、更少的代码等。
但是,流也有一些限制。它们最大的限制是只能使用一次。如果您是一名Java开发人员,上面提到的资源可以帮助您更深入地理解这些主题,因此请务必查看它们。
您可能还想了解Java中的异常处理。