博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA8新特性之:Stream 详解
阅读量:6874 次
发布时间:2019-06-26

本文共 7247 字,大约阅读时间需要 24 分钟。

hot3.png

Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。

Stream代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的。

Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的数据的访问和有效管理(增删改),而Stream并没有提供访问和管理元素的方式,而是通过声明数据源的方式,利用可计算的操作在数据源上执行,当然BaseStream.iterator() 和 BaseStream.spliterator()操作提供了遍历元素的方法。

Java Stream提供了提供了串行和并行两种类型的流,保持一致的接口,提供函数式编程方式,以管道方式提供中间操作和最终执行操作,为Java语言的集合提供了现代语言提供的类似的高阶函数操作,简化和提高了Java集合的功能。

Stream接口还包含几个基本类型的子接口如IntStream, LongStream 和 DoubleStream。

关于流和其它集合具体的区别,可以参照下面的列表:

  1. 不存储数据。流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
  2. 函数式编程。流的操作不会修改数据源,例如filter不会将数据源中的数据删除。
  3. 延迟操作。流的很多操作如filter,map等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行。
  4. 可以解绑。对于无限数量的流,有些操作是可以在有限的时间完成的,比如limit(n) 或 findFirst(),这些操作可是实现"短路"(Short-circuiting),访问到有限的元素后就可以返回。
  5. 纯消费。流的元素只能访问一次,类似Iterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流。

流的操作是以管道的方式串起来的。流管道包含一个数据源,接着包含零到N个中间操作,最后以一个终点操作结束。

并行 Parallelism

所有的流操作都可以串行执行或者并行执行。

除非显示地创建并行流,否则Java库中创建的都是串行流。 Collection.stream()为集合创建串行流而Collection.parallelStream()为集合创建并行流。IntStream.range(int, int)创建的是串行流。通过parallel()方法可以将串行流转换成并行流,sequential()方法将流转换成串行流。

除非方法的Javadoc中指明了方法在并行执行的时候结果是不确定(比如findAny、forEach),否则串行和并行执行的结果应该是一样的。

创建Stream

可以通过多种方式创建流:

1、通过集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()

2、通过Arrays.stream(Object[])方法, 比如Arrays.stream(new int[]{1,2,3})
3、使用流的静态方法,比如Stream.of(Object[])IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n * 2),或者generate(Supplier<T> s)Stream.generate(Math::random)
4、BufferedReader.lines()从文件中获得行的流。
5、Files类的操作路径的方法,如listfindwalk等。
6、随机数流Random.ints()
7、其它一些类提供了创建流的方法,如BitSet.stream()Pattern.splitAsStream(java.lang.CharSequence), 和 JarFile.stream()
8、更底层的使用StreamSupport,它提供了将Spliterator转换成流的方法。

中间操作

中间操作会返回一个新的流,并且操作是延迟执行的(lazy),它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。

distinct

distinct保证输出的流中包含唯一的元素,它是通过Object.equals(Object)来检查是否包含相同的元素。

下面的例子则使用 distinct 来找出不重复的单词。

List
l = Stream.of("a","b","c","b").distinct().collect(Collectors.toList());System.out.println(l); 输出结果:[a, b, c]

filter

filter返回的流中只包含满足断言(predicate)的数据。

下面的代码返回流中的偶数集合。

List
l = IntStream.range(1,10).filter( i -> i % 2 == 0).boxed().collect(Collectors.toList());System.out.println(l); 输出结果:[2, 4, 6, 8]

map

map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。

作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。

下面的代码中将字符元素映射成它的哈希码(ASCII值)。

List
l = Stream.of('a','b','c').map( c -> c.hashCode()).collect(Collectors.toList());System.out.println(l); 输出结果:[97, 98, 99]

flatmap

从上面例子可以看出,map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。

Stream
> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) );List
outputList = inputStream.flatMap((childList) -> childList.stream()).collect(Collectors.toList());输出结果:[1,2,3,4,5,6]

将最底层元素抽出来放到一起,最终 output 的新 Stream 里面。

limit;skip

limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素

List
l = IntStream.range(1,100).limit(5).skip(1) .boxed()//IntStream转换成Stream
.collect(Collectors.toList());System.out.println(l);输出结果:[2, 3, 4, 5]

peek

peek 对每个元素执行操作并返回一个新的 Stream

Stream.of("one", "two", "three", "four").filter(e -> e.length() > 3) .peek(e -> System.out.println("Filtered value: " + e)) .map(String::toUpperCase) .peek(e -> System.out.println("Mapped value: " + e)) .collect(Collectors.toList());输出结果:Filtered value: threeMapped value: THREEFiltered value: fourMapped value: FOUR

forEach 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环。

sorted

sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。

sorted(Comparator<? super T> comparator)可以指定排序的方式。

对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。

降序排列:List
li = Stream.of("c","d", "b", "a").sorted((s1,s2) ->s2.compareTo(s1)).collect(Collectors.toList());System.out.println(li);输出结果:[d, c, b, a]升序排序:List
li = Stream.of("c","d", "b", "a").sorted((s1,s2) ->s1.compareTo(s2)).collect(Collectors.toList());System.out.println(li);输出结果:[a, b, c, d]

终点操作

Match

这一组方法用来检查流中的元素是否满足断言。

allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true
anyMatch只有在任意一个元素满足断言时就返回true,否则flase,
noneMatch只有在所有的元素都不满足断言时才返回true,否则flase,

System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); 输出结果:trueSystem.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); 输出结果:trueSystem.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); 输出结果:falseSystem.out.println(Stream.
empty().allMatch( i -> i > 0)); 输出结果:trueSystem.out.println(Stream.
empty().anyMatch( i -> i > 0));输出结果:falseSystem.out.println(Stream.
empty().noneMatch( i -> i > 0));输出结果:true

count

count方法返回流中的元素的数量

System.out.println(Stream.of("c","d", "b", "a").count());输出结果:4

collect

使用一个collector执行mutable reduction操作。辅助类提供了很多的collector,java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。

System.out.println(Stream.of("c","d", "b", "a").collect(Collectors.toList()));输出结果:[c, d, b, a]

find

findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。

findFirst()返回第一个元素,如果流为空,返回空的Optional。

String l = Stream.of("cd","dc", "acb", "ade").filter(e -> e.contains("a")).findFirst().get();System.out.println(l);输出结果:acb

forEach、forEachOrdered

forEach遍历流的每一个元素,执行指定的action。它是一个终点操作,和peek方法不同。这个方法不担保按照流的encounter order顺序执行,如果对于有序流按照它的encounter order顺序执行,你可以使用forEachOrdered方法。

Stream.of(1,2,3).forEach(System.out::println);输出结果:123

max、min

max返回流中的最大值,

min返回流中的最小值。

System.out.println(IntStream.range(1,10).max().getAsInt());System.out.println(IntStream.range(1,10).min().getAsInt());输出结果:91

reduce

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于

Integer sum = integers.reduce(0, (a, b) -> a+b); 或

Integer sum = integers.reduce(0, Integer::sum);

也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

Integer total = Stream.of(2,1,3,4,5).reduce( (x, y) -> x +y).get();System.out.println(total);Integer total2 = Stream.of(2,1,3,4,5).reduce(2, (x, y) -> x +y);System.out.println(total2);输出结果:1517

toArray

将流中的元素放入到一个数组中。

并行流

像上面所说的,流操作可以是串行的,也可以是并行的。串行操作通过单线程执行,而并行操作则通过多线程执行。

下面的例子就演示了如何使用并行流进行操作来提高运行效率,代码非常简单。
首先我们创建一个大的list,里面的元素都是唯一的:

int max = 1000000;List
values = new ArrayList<>(max);for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString());}

现在,我们测量一下对这个集合进行排序所使用的时间。

串行排序

long t0 = System.nanoTime();long count = values.stream().sorted().count();System.out.println(count);long t1 = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);System.out.println(String.format("sequential sort took: %d ms", millis));// sequential sort took: 899 ms

并行排序

long t0 = System.nanoTime();long count = values.parallelStream().sorted().count();System.out.println(count);long t1 = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);System.out.println(String.format("parallel sort took: %d ms", millis));// parallel sort took: 472 ms

如你所见,所有的代码段几乎都相同,唯一的不同就是把stream()改成了parallelStream(), 结果并行排序快了50%。

转载于:https://my.oschina.net/luoyezhuifeng/blog/798212

你可能感兴趣的文章
MySQL入门(四)
查看>>
详解 ML2 Core Plugin(II) - 每天5分钟玩转 OpenStack(72)
查看>>
httpd-2.2 配置及用法完全攻略
查看>>
IntelliJ_编译一直报错“找不到符号”
查看>>
【Mongodb】3.X 配置身份验证
查看>>
云计算就像马拉松 京东CTO为啥这么说
查看>>
2017阿里UCAN大会,听听大咖们都讲了啥
查看>>
每日一淘完成1.3亿美元融资:元生资本与DCM领投
查看>>
「每天一道面试题」Java虚拟机为新生对象分配内存有哪两种方式?
查看>>
海信电器于芝涛:坚守画质 才是消费者首选
查看>>
甘肃庆阳黄土塬告别“土炕”“火炉” 多措治污享蓝天
查看>>
NPM实用指北
查看>>
直播竞答必读:一定要提前知道的技术坑和新玩法
查看>>
React 中集成 Markdown编辑器
查看>>
Spring Boot 最佳实践(五)Spring Data JPA 操作 MySQL 8
查看>>
由三道题引伸出来的思考
查看>>
React 开发实战(一)- Repeat 组件
查看>>
小程序云开发全套实战教程(最全)
查看>>
单页引用中使用百度地图
查看>>
对 PHP 中依赖注入和控制反转的理解
查看>>