文章目录
1、什么是流(Stream)
比较文献的定义,Stream是从支持数据处理操作的源生成的元素序列。根据对流(Stream)的定义,我们可以从中提取以下部分:
元素序列: 流为特定元素类型的一组有序值提供接口。 但是,流实际上并不存储元素; 它们是按需计算的。
源: 流来自数据提供源,例如集合,数组或I / O资源。
数据处理操作: Streams支持类似SQL的操作和函数式编程语言的常用操作,例如filter,map,reduce,find,match,sorted等。
此外,流操作有两个基本特征
流水线: 许多流操作本身返回一个流。 这允许操作被链接以形成更大的管道。 这可以实现某些优化,例如懒惰和短路。流水线的操作可以看作对数据源进行数据库式的查询。
内部迭代: 与显式迭代的集合(外部迭代)相比,流操作是在背后进行的。
2、为什么需要流(Stream)
Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
Stream API 借助于新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
3、Java 流(Stream) 和 集合(Collection)
- 集合(Collection)是一个内存中的数据结构,它存储包含着所有的值,每一个元素都是存放在内存里的
- 流(Stream)是概念上的固定的数据结构,元素都是按需计算的。从另一个角度来说,流就是延迟创建的集合,只要在需要的时候才会计算值,得到结果。
- 集合(Collection)需要显式迭代(Iterator),也就是外部迭代,能显式地一个一个遍历元素并对其执行某些操作。而流(Stream)使用的是内部迭代,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
基于以上几点,我们列出了Stream的各种特性,如下:
- 不是数据结构
- 专为lambdas设计
- 不支持索引访问
- 可以轻松输出为数组或列表
- 支持延迟访问
- 可并行
4、流(Stream)的构成
当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。
5、流(Stream)的使用
5.1、流(Stream)的操作类型
流的操作类型分为两种,中间操作和终端操作:
中间操作(Intermediate): 一个流可以后面跟随零个或多个中间 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
终端操作(Terminal): 一个流只能有一个终端操作,当这个操作执行后,流就无法再被操作。所以这是流的最后一个操作。终端操作执行,才会真正开始流的遍历,并且会生成一个结果。
常见的中间操作:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
常见的终端操作:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
5.2、流(Stream)的创建
有许多方法可以创建不同源的流实例。 一旦创建,实例将不会修改其源,因此允许从单个源创建多个实例。
5.2.1、空流(Empty Stream)
如果创建空流,则应使用empty()方法:
Stream<String> streamEmpty = Stream.empty();
通常情况下,在创建时使用empty()方法以避免为没有元素的流返回null:
public Stream<String> streamOf(List<String> list) {
return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}
5.2.2、通过集合(Collection)创建
Stream 也可以通过任何类型的Collection(Collection,List,Set)创建:
Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();
5.2.3、通过数组(Array)创建
Array也可以是Stream的源:
Stream<String> streamOfArray = Stream.of("a", "b", "c");
也可以从现有数组或数组的一部分创建:
String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
5.2.4、通过构造器 Stream.builder() 创建
使用构建器时,应在语句的右侧部分另外指定所需类型,否则build()方法将创建Stream 的实例:
Stream<String> streamBuilder =
Stream.<String>builder().add("a").add("b").add("c").build();
5.2.5、通过 Stream.generate() 创建
generate()方法接受Supplier 以生成元素。 由于生成的流是无限的,开发人员应指定所需的大小,否则generate()方法将一直有效,直到达到内存限制:
Stream<String> streamGenerated =
Stream.generate(() -> "element").limit(10);
5.2.6、通过 Stream.iterate() 创建
创建无限流的另一种方法是使用iterate()方法:
Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);
结果流的第一个元素是iterate()方法的第一个参数。 为了创建每个后续元素,指定的函数将应用于前一个元素。 在上面的示例中,第二个元素将是42。
5.2.7、原始流(Stream)
Java 8 对于基本数值型 提供了三种对应的包装类型 Stream:IntStream,LongStream,DoubleStream。当然我们也可以用 Stream、Stream >、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。
IntStream intStream = IntStream.range(1, 3);
LongStream longStream = LongStream.rangeClosed(1, 3);
range(int startInclusive,int endExclusive)方法创建从第一个参数到第二个参数的有序流。 它增加后续元素的值,步长等于1.结果不包括最后一个参数,它只是序列的上限。
rangeClosed(int startInclusive,int endInclusive)方法, 第二个元素包含在内。 这两种方法可用于生成三种类型的流中的任何一种。
5.2.8、通过 String 创建
String也可以用作创建流的源。
借助String类的chars()方法。 由于JDK中没有接口CharStream,因此IntStream用于表示字符流。
IntStream streamOfChars = "abc".chars();
以下示例根据指定的RegEx将String拆分:
Stream<String> streamOfString =
Pattern.compile(", ").splitAsStream("a, b, c");
5.2.9、通过 File 创建
Java NIO类Files允许通过lines()方法生成文本文件的Stream 。 文本的每一行都成为流的一个元素:
Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset =
Files.lines(path, Charset.forName("UTF-8"));
5.3、流(Steam)的使用示例
5.3.1、中间操作
5.3.1.1、Stream.filter()
过滤器根据给定的谓词过滤流的所有元素,并返回一个新的流。
//构造数据
public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));
public static void main(String[] args) {
//filter
appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
}
5.3.1.2、Stream.map()
对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素
以下示例将每个苹果重量加5。 你也可以使用map将每个对象转换为另一种类型。
//构造数据
public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));
public static void main(String[] args) {
//filter
//appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
// map
appleList.stream().map(a->a.getWeight()+5).forEach(System.out::println);
}
5.3.1.3、Stream.flatMap()
map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。
//构造数据
public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));
public static void main(String[] args) {
// //filter——过滤
// appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
// // map——映射(一对一)
// appleList.stream().map(a -> a.getWeight() + 5).forEach(System.out::println);
// //sorted——排序
// appleList.stream().sorted(Comparator.comparing(Apple::getWeight)).forEach(System.out::println);
//flatMap
appleList.stream().flatMap(a -> Stream.of("苹果颜色:".concat(a.getColor()))).forEach(System.out::println);
}
5.3.1.4、Stream.sorted()
对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
//构造数据
public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));
public static void main(String[] args) {
// //filter——过滤
// appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
// // map——映射(一对一)
// appleList.stream().map(a -> a.getWeight() + 5).forEach(System.out::println);
// sorted——排序
appleList.stream().sorted(Comparator.comparing(Apple::getWeight)).forEach(System.out::println);
}
5.3.2、终端操作
终端操作返回某种类型的结果,而不是返回Stream。
5.3.2.1、Stream.forEach()
Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。
//forEach
appleList.stream().forEach(System.out::println);
5.3.2.2、Stream.collect()
collect()方法用于从steam 收集元素并将它们存储在集合中。
//collect
appleList.stream().map(a -> a.getColor().equals("green")).collect(Collectors.toList());
5.3.2.3、Stream.match()
Stream 有三个 match 方法:
- allMatch:Stream 中全部元素符合传入的 predicate,返回 true
- anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
- noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
//anyMatch
appleList.stream().anyMatch(a->a.getColor().contains("g"));
//noneMatch
appleList.stream().noneMatch(a->a.getColor().contains("g"));
//allMatch
appleList.stream().allMatch(a->a.getColor().contains("g"));
5.3.2.4、Stream.count()
返回流中元素的数量为long。
//count
appleList.stream().count();
5.3.2.5、Stream.reduce()
这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);
参考资料:
https://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html
Java 8 实战