Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8 中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。

Stream 定义

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作。
Streams的思想很简单,就是遍历。

如何使用 Stream?

对 Stream 的使用可以分为3步:

创建 Stream:通过 stream() 方法,取得集合对象的数据集。
Intermediate(可以多次):通过一系列中间(Intermediate)方法,对数据集进行过滤、检索等数据集的再次处理。
Terminal(只有一次):通过最终(terminal)方法完成对数据集中元素的最终处理。

在一次聚合操作中,可以有多个Intermediate,但是有且只有一个Terminal。
也就是说,在对一个 Stream 可以进行多次转换操作,并不是每次都对 Stream 的每个元素执行转换。
并不像 for 循环中,循环 N 次,其时间复杂度就是 N。转换操作是 lazy (惰性求值)的,只有在 Terminal 操作执行时,才会一次性执行。
可以这么认为,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

Stream的操作分类

刚才提到的Stream的操作有Intermediate、Terminal和Short-circuiting:

Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered
Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 iterator
Short-circuiting:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

Short-circuiting 是短路操作,其和中间操作与终结操作都有包含,limit 属于短路和中间操作;anyMatch 等属于短路和终结操作。
由无限的流,转到有限的流,不管是中间操作还是终结操作都叫做短路。如果想终结无限流操作,用短路是必须的,但并不足够(例如 limit),可能还需要其他的流操作(例如 forEach)。

惰性求值和及早求值方法

像 filter 这样只描述 Stream,最终不产生新集合的方法叫作惰性求值方法;而像 count 这样最终会从Stream产生值的方法叫作及早求值方法。看其返回值就可判断一个操作是惰性求值还是及早求值:如果返回值是 Stream,那么就是惰性求值;如果返回值不是 Stream 或者是 void,那么就是及早求值。同样在一个 Stream 操作中,可以有多次惰性求值,但有且仅有一次及早求值。

创建 Stream

有多种方式生成Stream:

Stream 接口的静态工厂方法(注意:Java8 里接口可以带静态方法);

Collection 接口和数组的默认方法(默认方法,也使 Java 的新特性之一,后续介绍),把一个 Collection 对象转换成 Stream;

其他
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()

Stream 接口的静态工厂方法

of 方法

其生成的 Stream 是有限长度的,Stream 的长度为其内的元素个数。

of(T... values):返回含有多个 T 元素的 Stream
of(T t):返回含有一个 T 元素的 Stream

示例:

// 1. 对象
Stream stream = Stream.of("a", "b", "c");
// 2. 数组
String[] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. 集合
List<String> list = Arrays.asList(strArray);
stream = list.stream();

生成 DoubleSteam、IntSteram 或 LongStream 对象(这是目前支持的三个数值类型 Stream 对象)

IntStream.of(new int[]{1, 2, 3}); // 根据数组生成
IntStream.range(1, 3); // 按照范围生成,不包括3
IntStream.rangeClosed(1, 3); // 按照范围生成,包括3

generate 方法

返回一个无限长度的 Stream,其元素由 Supplier 接口的提供。在 Supplier 是一个函数接口,只封装了一个 get() 方法,其用来返回任何泛型的值,该结果在不同的时间内,返回的可能相同也可能不相同,没有特殊的要求。

generate(Supplier<T> s):返回一个无限长度的Stream

这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。
把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。

Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return java.lang.Math.random();
}
}).limit(10).forEach(System.out::println); Stream.generate(()-> java.lang.Math.random()).limit(10).forEach(System.out::println);
Stream.generate(java.lang.Math::random).limit(10).forEach(System.out::println);

以上三种形式达到的效果是一样的,只不过是下面的两个采用了 Lambda 表达式,简化了代码,其实际效果就是返回一个随机值。一般无限长度的 Stream 会与 filter、limit 等配合使用,否则 Stream 会无限制的执行下去。

iterate 方法

其返回的也是一个无限长度的 Stream,与 generate 方法不同的是,其是通过函数f迭代对给指定的元素种子而产生无限连续有序 Stream,其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环。

- iterate(T seed, UnaryOperator<T> f)

示例

Stream.iterate(1, item -> item + 1)
.limit(10)
.forEach(System.out::println);
// 打印结果:1,2,3,4,5,6,7,8,9,10

上面示例,种子为 1,也可认为该 Stream 的第一个元素,通过 f 函数来产生第二个元素。接着,第二个元素,作为产生第三个元素的种子,从而产生了第三个元素,以此类推下去。需要主要的是,该 Stream 也是无限长度的,应该使用 filter、limit 等来截取 Stream,否则会一直循环下去。

empty 方法

返回一个空的顺序 Stream,该 Stream 里面不包含元素项。

Collection 接口和数组的默认方法

在 Collection 接口中,定义了一个默认方法 stream(),用来生成一个 Stream。

public interface Collection<E> extends Iterable<E> {
***
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
***
}

在 Arrays 类,封装了一些列的 Stream 方法,不仅针对于任何类型的元素采用了泛型,更对于基本类型作了相应的封装,以便提升 Stream 的处理效率。

public class Arrays {
***
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
} public static LongStream stream(long[] array) {
return stream(array, 0, array.length);
}
***

示例

int ids[] = new int[]{1, 2, 3, 4};
Arrays.stream(ids).forEach(System.out::println);

Intermediate

Intermediate 主要是用来对 Stream 做出相应转换及限制流,实际上是将源Stream转换为一个新的 Stream,以达到需求效果。

concat

concat 方法将两个 Stream 连接在一起,合成一个 Stream。若两个输入的 Stream 都时排序的,则新 Stream 也是排序的;若输入的 Stream 中任何一个是并行的,则新的 Stream 也是并行的;若关闭新的 Stream 时,原两个输入的 Stream 都将执行关闭处理。

Stream.concat(Stream.of(1, 2, 3), Stream.of("a", "b", "c"))
.forEach(integer -> System.out.print(integer + " "));
// 打印结果:1 2 3 a b c

distinct

distinct方法以达到去除掉原Stream中重复的元素,生成的新Stream中没有没有重复的元素。

Stream.of(1,2,3,1,2,3)
.distinct()
.forEach(System.out::println); // 打印结果:1,2,3

filter

filter 方法对原 Stream 按照指定条件过滤,在新建的 Stream 中,只包含满足条件的元素,将不满足条件的元素过滤掉。

Stream.of(1, 2, 3, 4, 5)
.filter(item -> item > 3)
.forEach(System.out::println);// 打印结果:4,5

filter 传入的 Lambda 表达式必须是 Predicate 实例,参数可以为任意类型,而其返回值必须是 boolean 类型。

map

map 方法将对于 Stream 中包含的元素使用给定的转换函数进行转换操作,新生成的 Stream 只包含转换生成的元素。为了提高处理效率,官方已封装好了,三种变形:mapToDouble,mapToInt,mapToLong。其实很好理解,如果想将原 Stream 中的数据类型,转换为double,int 或者是 long 是可以调用相对应的方法。

Stream.of("a", "b", "hello")
.map(item-> item.toUpperCase())
.forEach(System.out::println);
// 打印结果
// A, B, HELLO

传给 map 中 Lambda 表达式,接受了 String 类型的参数,返回值也是 String 类型,在转换行数中,将字母全部改为大写。

map 传入的 Lambda 表达式必须是 Function 实例,参数可以为任意类型,而其返回值也是任性类型,javac 会根据实际情景自行推断。

peek

peek 方法生成一个包含原 Stream 的所有元素的新 Stream,同时会提供一个消费函数(Consumer实例),新 Stream 每个元素被消费的时候都会执行给定的消费函数,并且消费函数优先执行。

Stream.of(1, 2, 3, 4, 5)
.peek(integer -> System.out.println("accept:" + integer))
.forEach(System.out::println);
// 打印结果
// accept:1
// 1
// accept:2
// 2
// accept:3
// 3
// accept:4
// 4
// accept:5
//

skip

skip 方法将过滤掉原 Stream 中的前N个元素,返回剩下的元素所组成的新 Stream。如果原 Stream 的元素个数大于 N,将返回原 Stream 的后(原Stream长度-N)个元素所组成的新 Stream;如果原 Stream 的元素个数小于或等 于N,将返回一个空 Stream。

Stream.of(1, 2, 3, 4, 5)
.skip(2)
.forEach(s -> System.out.print(s + " "));
// 打印结果:3 4 5

sorted

sorted 方法将对原 Stream 进行排序,返回一个有序列的新 Stream。sorterd 有两种变体sorted(),sorted(Comparator),前者将默认使用 Object.equals(Object)进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。

Stream.of(1, 5, 4, 2, 3)
.sorted()
.forEach(System.out::print);
// 打印结果:12345
System.out.println();
Stream.of(1, 5, 4, 2, 3)
.sorted((o1, o2) -> o2 - o1)
.forEach(System.out::print);
// 打印结果:543212345

Terminal

count

count 方法将返回Stream中元素的个数。

long count = Stream.of(1, 2, 3, 4, 5)
.count();
System.out.println("count:" + count);// 打印结果:count:5

forEach

forEach方法前面已经用了好多次,其用于遍历Stream中的所元素,避免了使用for循环,让代码更简洁,逻辑更清晰。

Stream.of(5, 4, 3, 2, 1)
.sorted()
.forEach(System.out::println);
// 打印结果:12345

forEachOrdered

forEachOrdered 方法与 forEach 类似,都是遍历 Stream 中的所有元素,不同的是,如果该 Stream 预先设定了顺序,会按照预先设定的顺序执行(Stream是无序的),默认为元素插入的顺序。

Stream.of(5,2,1,4,3)
.forEachOrdered(integer -> {
System.out.println("integer:"+integer);
});
// 打印结果
// integer:5
// integer:2
// integer:1
// integer:4
// integer:3

max、min

max\min方法根据指定的Comparator,返回一个Optional,该Optional中的value值就是Stream中最大\最小的元素。

原 Stream 根据比较器 Comparator,进行排序(升序或者是降序)。max 就是取重新排序后的最后一个值,而min取排序后的第一个值。

Optional<Integer> max = Stream.of(1, 2, 3, 4, 5)
.max((o1, o2) -> o2 - o1);
System.out.println("max:" + max.get());// 打印结果:max:1 Optional<Integer> min = Stream.of(1, 2, 3, 4, 5)
.min((o1, o2) -> o2 - o1);
System.out.println("min:" + min.get());// 打印结果:min:5

对于原 Stream 指定了Comparator(降序),max就是新序列最后一个即实际上的最小值,min就是新序列第一个即实际上的最大值,不管是最大值还是最小值起决定作用的是 Comparator,它决定了元素比较大小的原则。

collect

参考:https://blog.csdn.net/IO_Field/article/details/54971608

reduce

参考:https://blog.csdn.net/IO_Field/article/details/54971679

Short-circuiting

allMatch

allMatch 操作用于判断 Stream 中的元素是否全部满足指定条件。如果全部满足条件返回 true,否则返回 false。

boolean allMatch = Stream.of(1, 2, 3, 4)
.allMatch(integer -> integer > 0);
System.out.println("allMatch: " + allMatch); // 打印结果:allMatch: true

anyMatch

anyMatch 操作用于判断 Stream 中的是否有满足指定条件的元素。如果最少有一个满足条件返回 true,否则返回 false。

boolean anyMatch = Stream.of(1, 2, 3, 4)
.anyMatch(integer -> integer > 3);
System.out.println("anyMatch: " + anyMatch); // 打印结果:anyMatch: true

noneMatch

noneMatch 方法将判断 Stream 中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回 true;否则,返回 false.

boolean noneMatch = Stream.of(1, 2, 3, 4, 5)
.noneMatch(integer -> integer > 10);
System.out.println("noneMatch:" + noneMatch); // 打印结果 noneMatch:true boolean noneMatch_ = Stream.of(1, 2, 3, 4, 5)
.noneMatch(integer -> integer < 3);
System.out.println("noneMatch_:" + noneMatch_); // 打印结果 noneMatch_:false

findAny

findAny 操作用于获取含有 Stream 中的某个元素的 Optional,如果 Stream 为空,则返回一个空的 Optional。由于此操作的行动是不确定的,其会自由的选择 Stream 中的任何元素。在并行操作中,在同一个 Stram 中多次调用,可能会不同的结果。在串行调用测试时,每次都是获取的第一个元素。

Optional<Integer> any = Stream.of(1, 2, 3, 4).findAny();
System.out.println(any.get());

findFirst

findFirst 操作用于获取含有 Stream 中的第一个元素的 Optional,如果 Stream 为空,则返回一个空的 Optional。若 Stream 并未排序,可能返回含有 Stream 中任意元素的 Optional。

Optional<Integer> any = Stream.of(1, 2, 3, 4).findFirst();

limit

limit 方法将截取原 Stream,截取后 Stream 的最大长度不能超过指定值 N。如果原 Stream 的元素个数大于 N,将截取原 Stream 的前 N 个元素;如果原 Stream 的元素个数小于或等于 N,将截取原 Stream 中的所有元素。

参考:https://www.cnblogs.com/Fndroid/p/6087380.html

http://www.runoob.com/java/java8-streams.html

https://blog.csdn.net/io_field/article/details/54971761

Java 8 特性 —— Stream的更多相关文章

  1. java新特性stream

    java新特性stream,也称为流式编程. 在学习stream之前先了解一下java内置的四大函数 第一种函数式函数,后面是lambda表达式写法 /*Function<String,Inte ...

  2. Java 8 新特性---------Stream

    Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据. Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象 ...

  3. JAVA 8 新特性Stream初体验

    什么是 Stream? Stream(流)是一个来自数据源的元素队列并支持聚合操作 <strong元素队列< strong="">元素是特定类型的对象,形成一个队 ...

  4. Java 8新特性--Stream API

    Java 8 API添加了一个新的抽象称为流Stream,以一种声明的方式处理数据,可以极大提高程序员的生产力,写出高效.干净.简洁的代码.这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可 ...

  5. 这可能是史上最好的 Java8 新特性 Stream 流教程

    本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...

  6. java8新特性--Stream的基本介绍和使用

    什么是Stream? Stream是一个来自数据源的元素队列并可以进行聚合操作. 数据源:流的来源. 可以是集合,数组,I/O channel, 产生器generator 等 聚合操作:类似SQL语句 ...

  7. Java高级特性 第1节 集合框架和泛型

    Java中,存储多个同类型的数据,可以用数组来实现,但数组有一些缺陷: 数组长度固定不变,布恩那个很好的适应元素数量动态变化的情况 可以通过数组.length获取数组长度,却无法直接获取数组中实际存储 ...

  8. Java 8特性尝鲜:新新IO

    Java 8特性尝鲜:新新IO 在这个专题前面的文章中,我们已经看到,使用Java8的lambda表达式对现有的JDK1.2 I/O库的提升,主要是可以使用lambda表达式来构造java.io.Fi ...

  9. JDK1.8新特性——Stream API

    JDK1.8新特性——Stream API 摘要:本文主要学习了JDK1.8的新特性中有关Stream API的使用. 部分内容来自以下博客: https://blog.csdn.net/icarus ...

随机推荐

  1. Ajax的面试题

    一.什么事Ajax?为什么要用Ajax?(谈谈对Ajax的认识) 什么是Ajax: Ajax是“Asynchronous JavaScript and XML”的缩写.他是指一种创建交互式网页应用的网 ...

  2. Webpack 4教程:为什么要优化代码

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://wanago.io/2018/07/30/webpack-4-course-part ...

  3. Android-蓝牙自动配对与隐藏对话框

    一.概述 本次分析是基于Android7.0的源码. 二.自动配对分析过程 首先,我们分析一下源码的自动配对过程,packages/apps/Settings/src/com/android/sett ...

  4. 项目实战工具类(二):ZipUtils(压缩/解压缩文件相关)

    import android.content.Context; import android.util.Log; import java.io.File; import java.io.FileInp ...

  5. 关于boostrap的modal隐藏问题(前端框架)

    Modal(模态框) 首先,外引boostrap和Jquery的文件环境: <link rel="stylesheet" href="https://cdn.sta ...

  6. 微信小程序开发基础

    前言: 微信小程序开入入门,如果你有html+css+javascript的基础,那么你就很快地上手掌握的.下面提供微信小程序官方地址:https://developers.weixin.qq.com ...

  7. SQL Server服务没有自动启动原因案例分析

    这个案例是前两天出现的,一直没有时间总结,25号凌晨4点去处理数据库的故障问题.远程连上公司的局域网,psping检查发现服务器的1433端口不通,数据库连接不上,但是主机又能ping通,登录服务器检 ...

  8. C#图片添加文字水印

    /// <summary> /// 给图片添加文字水印 /// </summary> /// <param name="img">图片</ ...

  9. VirtualBox Network Config

    Sharing Host VPN with VirtualBox guest After looking for this solution everywhere, I finally found a ...

  10. Linux常用保护机制

    Linux程序常见用的一些保护机制 一.NX(Windows中的DEP) NX:No-eXecute.DEP:Data Execute Prevention 也就是数据不可执行,防止因为程序运行出现溢 ...