关于java中Stream理解
关于java中Stream理解
Stream是什么
Stream:Java 8新增的接口,Stream可以认为是一个高级版本的Iterator。它代表着数据流,流中的数据元素的数量可以是有限的,
也可以是无限的。
Stream跟Iterator的差别是
无存储:流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
函数式编程:对数据流的任何修改都不会修改背后的数据源,比如对流执行滤波器操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新的流。
惰性执行:Stream的操作由零个或多个中间操作(中间操作)和一个结束操作(terminal operation)两部分组成。只有执行了结束操作,Stream定义的中间操作才会依次执行,这就是Stream延迟特性。从后往前追溯,直到起始。
惰性求值(lazy evaluation):惰性求值是一种将对函数或请求的处理延迟到真正需要结果时进行求值的方式。
可消费性:流只能被“消费”一次,一旦遍历过就会失效就像容器的迭代器那样,想要再次遍历必须重新生成一个新的数据流。
对stream的操作分为为两类,二者特点是:
中间操作(intermediate operations) 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。
结束操作(terminal operations) 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。
为什么要使用stream
代码简洁,函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。
多核友好,Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法。
源码解析与使用示例
1.如何创建Stream
Collection.stream()或者Collection.parallelStream()
调用Arrays.stream(T[] array)方法。
使用Stream.builder()使用.add(x)添加元素使用build()完成构建
Stream.of来创建
2.操作方式
以下是部分常用操作方式
| 操作类型 | 接口方法 |
|---|---|
| 中间操作 | caoncat() distinct() limit() peek() skip() sorted() filter() parallel() sequential() unordered() map() flatMap() |
| 结束操作 | forEach() forEachOrdered() toArray() findAny() findFirst() allMatch() anyMatch() noneMatch() reduce() max() min() count() collect() |
通常情况下Stream和函数接口关系非常紧密,没有函数接口steam就无法工作。而函数接口出现的地方都可以使用lambda表达式.
中间操作
在讲解操作之前先构造一些基础的实体类和处理逻辑
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Student {
private Integer id;
private String name;
private Boolean sex;
private Double height;
}
//原始数据
private static List<Student> getWList() {
Student s7 = new Student(7,"貂蝉",false,1.65);
Student s8 = new Student(8,"小乔",false,1.61);
Student s9 = new Student(8,"小乔",false,1.61);
List<Student> wList = new ArrayList<>();
wList.add(s7);
wList.add(s8);
wList.add(s9);
return wList;
}
private static List<Student> getMList() {
Student s1 = new Student(1,"刘备",true,1.72);
Student s2 = new Student(2,"关羽",true,1.80);
Student s3 = new Student(3,"张飞",true,1.82);
Student s4 = new Student(4,"黄忠",true,1.81);
Student s5 = new Student(5,"马超",true,1.70);
Student s6 = new Student(6,"赵云",true,1.75);
List<Student> mList = new ArrayList<>();
mList.add(s1);
mList.add(s2);
mList.add(s3);
mList.add(s4);
mList.add(s5);
mList.add(s6);
return mList;
}
每次调用方法都会创建新的list对象,不会出现list耗尽的情况
在学习方法之前有一个新的操作符需要理解
:: 操作符: 表示调用该对象的某方法 Student::getHeight 等同于 x->x.getgetHeight() 等同于 (x) -> {return x.getHeight()} System.out::println
等同于x -> System.out.println(x)
1. 连接 caoncat()
创建一个延迟的连接流
接口
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
操作过程
// 合并 concat()
Stream<Student> stream = Stream.concat(mList.stream(), wList.stream());
// 结束操作
streamSorted.forEach(System.out::println);
打印结果:
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)
Student(id=7, name=貂蝉, sex=false, height=1.65)
Student(id=8, name=小乔, sex=false, height=1.61)
Student(id=8, name=小乔, sex=false, height=1.61)
打印过结果显示是该流被整合在了一起
2. 去重 distinct()
除去重复元素,如果是对比对象内容需要重写equals方法
接口
Stream<T> distinct();
具体实现
// 去重 distinct()
Stream<Student> streamDistinct = wList.stream().distinct();
streamSorted.forEach(System.out::println);
打印结果:
Student(id=7, name=貂蝉, sex=false, height=1.65)
Student(id=8, name=小乔, sex=false, height=1.61)
可以看出多余的小乔被移除了
3.截断 limit()
截断流,使其元素不超过给定数量,可以理解为for循环执行到一定数量以后break。
接口
Stream<T> limit(long maxSize);
具体实现
// 截断 limit()
Stream<Student> streamLimit = mList.stream().limit(3);
streamSorted.forEach(System.out::println);
打印结果
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
可以看出流满足三个元素以后就被截断了。
4.跳过 skip()
跳过几个元素,返回一个丢掉了前n个元素的流,若元素不足n个则返回一个空的流。与limit()互补。
接口
Stream<T> skip(long n);
具体实现
// 跳过 skip
Stream<Student> streamSkip = mList.stream().skip(3);
streamSkip.forEach(x -> System.out.println(x.toString()));
打印结果
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)
可以看出前三个元素被丢掉了
limit方法时从开始到limit的参数位置个元素留下其他的丢掉。skip方法时从开始到skip的参数个元素丢掉其他的留下。
5.排序 sorted
将元素按照某种既定的顺序排序
接口
// 默认比较器 值可排序的情况下
Stream<T> sorted();
// 比较器排序 自定义比较器
Stream<T> sorted(Comparator<? super T> comparator);
实现方式 应为student是对象无法使用默认比较器。
System.out.println("排序 sorted方法");
System.out.println("排序前 stream");
Stream<Student> streamSorted = getMList().stream();
streamSorted.forEach(System.out::println);
// 第一种自定义比较方法
System.out.println("排序后 stream");
streamSorted = getMList().stream().sorted((x,y)->{
if(x.getHeight().equals(y.getHeight())){
return x.getName().compareTo(y.getName());
}else{
return x.getHeight().compareTo(y.getHeight());
}
});
streamSorted.forEach(System.out::println);
System.out.println("排序后 stream");
// 第二种自定义比较方法
streamSorted = getMList().stream().sorted(Comparator.comparingDouble(Student::getHeight));
streamSorted.forEach(System.out::println);
打印结果
排序 sorted方法
排序前 stream
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)
排序后 stream
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=6, name=赵云, sex=true, height=1.75)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=3, name=张飞, sex=true, height=1.82)
6. 转化为并行流 parallel()
将程序并行执行,执行顺序并不是有序的。
接口
//该方法时存在于BaseStream
S parallel();
具体实现
Stream<Student> streamParallel = getMList().stream();
streamParallel.parallel().forEach(System.out::println);
打印结果
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)
Student(id=4, name=黄忠, sex=true, height=1.81)
可以看出以上执行顺序已经乱了。
7. 转化为顺序执行 sequential()
将流转换为顺序执行
接口
S sequential();
使用方式和 parallel类似,通常情况下流都是顺序执行的。
8. 标记无序化 unordered()
将这个流标记为无序,但是不会打乱其顺序。
接口
S unordered();
9.摊平 flatMap()
将一个一个list摊平为Student流
接口
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
实现方式
log.info("摊平 flatMap");
Stream<List<Student>> streamFlatMap1 = Stream.of(getWList(),getMList());
streamFlatMap1.forEach(System.out::println);
Stream<List<Student>> streamFlatMap = Stream.of(getWList(),getMList());
streamFlatMap.flatMap(Collection::stream).forEach(System.out::println);
打印结果
*[Student(id=7, name=貂蝉, sex=false, height=1.65),
Student(id=8, name=小乔, sex=false, height=1.61),
Student(id=8, name=小乔, sex=false, height=1.61)]
[Student(id=1, name=刘备, sex=true, height=1.72),
Student(id=2, name=关羽, sex=true, height=1.8),
Student(id=3, name=张飞, sex=true, height=1.82),
Student(id=4, name=黄忠, sex=true, height=1.81),
Student(id=5, name=马超, sex=true, height=1.7),
Student(id=6, name=赵云, sex=true, height=1.75)]
Student(id=7, name=貂蝉, sex=false, height=1.65)
Student(id=8, name=小乔, sex=false, height=1.61)
Student(id=8, name=小乔, sex=false, height=1.61)
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
Student(id=3, name=张飞, sex=true, height=1.82)
Student(id=4, name=黄忠, sex=true, height=1.81)
Student(id=5, name=马超, sex=true, height=1.7)
Student(id=6, name=赵云, sex=true, height=1.75)*
10. 转换映射 map()
就是对每个元素按照某种操作进行转换,转换前后Stream中元素的个数不会改变,但元素的类型取决于转换之后的类型。
接口
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
具体实现
log.info("转换映射 map");
Stream<Student> streamMap = getMList().stream();
//这里由一个Student流转换为一个double流
streamMap.map(Student::getHeight)
.peek(x-> System.out.println(x.getClass().getName()))
.forEach(System.out::println);
打印结果
09:52:08.665 [main] INFO stream.StreamApiExample - 转换映射 map
java.lang.Double
1.72
java.lang.Double
1.8
java.lang.Double
1.82
java.lang.Double
1.81
java.lang.Double
1.7
java.lang.Double
1.75
可以看出该流已经由Student流转换为一个double流了
结束操作
中间操作讲解完成了,其他的部分后续会补充。其实在讲解中间流的时候已经使用到一种结束操作,这就是遍历操作。
1.遍历操作 forEach() forEachOrdered()
如果流具有已定义的相遇顺序,则按流的相遇顺序对此流的每个元素执行操作。
主要区别在并行处理上,forEach方法不能保证在并行状态下流的执行是按照顺序出现的。而forEachOrdered()可以保证
接口
void forEachOrdered(Consumer<? super T> action);
void forEach(Consumer<? super T> action);
具体实现
IntStream.range(1,5).parallel().forEachOrdered(System.out::println);
IntStream.range(1,5).parallel().forEach(System.out::println);
打印结果
*1
2
3
4
1
2
4
3*
可以看到顺序被打乱了
注意:只有在并行状态下才会有区别。
2. 转换为数组 toArray()
将流对象转换为数组
Object[] toArray();
实现方式
Stream<Student> streamToArray = getMList().stream();
Object[] arr = streamToArray.toArray();
System.out.println(Arrays.toString(arr));
打印结果
[Student(id=1, name=刘备, sex=true, height=1.72),
Student(id=2, name=关羽, sex=true, height=1.8),
Student(id=3, name=张飞, sex=true, height=1.82),
Student(id=4, name=黄忠, sex=true, height=1.81),
Student(id=5, name=马超, sex=true, height=1.7),
Student(id=6, name=赵云, sex=true, height=1.75)]
3. 发现元素 findFirst() findAny()
在顺序的情况两个方法都是下返回第一个元素
在乱序的情况下 findFirst()返回的永远是顺序状态下的第一个元素。
findAny()返回的是第一个执行完的元素。
接口
Optional<T> findFirst();
Optional<T> findAny();
实现方式
log.info(" findFirst()和findAny");
Stream<Student> streamFindFirst = getMList().stream();
Optional<Student> stu = streamFindFirst.parallel().findFirst();
System.out.println(stu.get());
Stream<Student> streamFindAny = getMList().stream();
Optional<Student> stuAny = streamFindAny.parallel().findAny();
System.out.println(stuAny.get());
打印结果
Student(id=1, name=刘备, sex=true, height=1.72)
Student(id=2, name=关羽, sex=true, height=1.8)
4.条件全匹配 allMatch()noneMatch() anyMatch()
allMatch() 所有元素是否匹配该条件
noneMatch() 没有一个符合匹配条件
anyMatch() 至少有一个符合匹配条件
接口
//方法api
boolean test(T t);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
实现方式
log.info("allMatch()");
Stream<Student> streamAllMatch = getMList().stream();
boolean res = streamAllMatch.allMatch(x -> x.getId() < 100);
System.out.println(res);
打印结果
true
注意:如果流是空的任意匹配都返回true 空虚真理
5. 聚合操作 reduce() sum()、max()、min()、count()
reduce() 自定义聚合操作
max() 求最大
min() 求最小
count() 统计数量
接口
// 元素如何合并
Optional<T> reduce(BinaryOperator<T> accumulator);
// 顺序执行时 初始值 identity 元素如何合并操作 accumulator
T reduce(T identity, BinaryOperator<T> accumulator);
// 在并行执行时
<U> U reduce(U identity,//初始值
BiFunction<U, ? super T, U> accumulator, //如何合并成一部分
BinaryOperator<U> combiner); //多部分如何合并
// 求最大 传入比较器
Optional<T> max(Comparator<? super T> comparator);
// 求最小 传入比较器
Optional<T> min(Comparator<? super T> comparator);
//统计元素个数
long count();
使用示例1
log.info("reduce() 一般都和map配合使用");
log.info("统计身高平均值");
Stream<Student> streamReduce = getMList().stream();
long count = streamReduce.count();
Optional<Double> total = getMList().stream().map(Student::getHeight)
.reduce((a, b) -> a + b);
System.out.println(total.get()/count);
结果
1.7666666666666666
5.collect()
该方法可以解决大多数啊上面api解决不了的问题。
接口
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);
使用示例:
将student对象中的name作为key height作为value生成一个Map
Stream<Student> streamollect = getMList().stream();
Map<String,String> s = streamReduce.collect(HashMap::new,
(map,stu)-> map.put(stu.getName(),stu.getHeight()+""),
HashMap::putAll);
System.out.println(s.toString());
结果:
{关羽=1.8, 张飞=1.82, 刘备=1.72, 马超=1.7, 赵云=1.75, 黄忠=1.81}
将流对象中的名称取出形成一个列表
Stream<Student> streamCollect = getMList().stream();
ArrayList<Object> list = streamCollect.collect(
ArrayList::new,
(arr, stu) -> arr.add(stu.getName()),
ArrayList::addAll);
System.out.println(list.toString());
结果:
[刘备, 关羽, 张飞, 黄忠, 马超, 赵云]
执行过程
总结
collect的用法还有很多很多。从根本上将Stream是解决for循环不能并行优化的问题.而在这其中很多的lambda的方法都很违反常规的java写法。不过随着lambda的加入将会有越来越多的函数式编程方式来实现java的功能。
引用
https://github.com/kanwangzjm/JavaLambdaInternals
关于java中Stream理解的更多相关文章
- java中如何理解:其他类型 + string 与 自增类型转换和赋值类型转换
java中如何理解:其他类型 + string 与 自增类型转换和赋值类型转换 一.字符串与其他类型连接 public class DemoString{ public static void mai ...
- Java中stream的详细用法
来自于:Java 8 stream的详细用法_旅行者-CSDN博客_java stream 一.概述 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行 ...
- java中stream部分笔记
Stream流表面上看起来与集合类似,允许你转换和检索数据.然而,两者却有显著的不同1.流不存储元素.它们存储在底层的集合或者按需生成2.流操作不改变他们的源数据.例如filter方法不会从一个新流中 ...
- Java中Stream流相关介绍
什么是Stream? Stream是JDK8 API的新成员,它允许以声明性方式处理数据集合 特点 代码简洁: 函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环 多核友好 ...
- scala中Stream理解
// Stream:Stream is lazy List; // Stream惰性求值指它只确定第一个值,后面的值用到再求值,这样可以防止数据过大全部加载导致内存溢出 // 将Range转化成Str ...
- 第四节:详细讲解Java中的类和面向对象思想
前言 大家好,给大家带来详细讲解Java中的类和面向对象思想的概述,希望你们喜欢 类和面向对象 在Java中怎样理解对象,创建对象和引用:什么是引用,对于基础学习的同学,要深入了解引用.示例:Stri ...
- Java中的流(1)流简介
简介 1.在java中stream代表一种数据流(源),java.io的底层数据元.(比作成水管)2.InputStream 比作进水管,水从里面流向你,你要接收,read3.OutputStream ...
- Java:泛型的理解
本文源自参考<Think in Java>,多篇博文以及阅读源码的总结 前言 Java中的泛型每各人都在使用,但是它底层的实现方法是什么呢,为何要这样实现,这样实现的优缺点有哪些,怎么解决 ...
- 【转】输入/输出流 - 深入理解Java中的流 (Stream)
基于流的数据读写,太抽象了,什么叫基于流,什么是流?Hadoop是Java语言写的,所以想理解好Hadoop的Streaming Data Access,还得从Java流机制入手.流机制也是JAVA及 ...
随机推荐
- HTTP协议和SOCKS5协议
HTTP协议和SOCKS5协议 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们平时上网的时候基本上是离不开浏览器的,尤其是搜索资料的时候,那么这个浏览器是如何工作的呢?用的又是 ...
- 在SpringBoot2.0及Spring 5.0 WebMvcConfigurerAdapter已被废弃,目前找到解决方案就有两种
1 直接实现WebMvcConfigurer (官方推荐) 例如: @Configuration public class WebMvcConfg implements WebMvcConfigure ...
- 1、JDBC-Connection
新建Maven工程 pom.xml <dependencies> <dependency> <groupId>mysql</groupId> <a ...
- Oracle记录-Linux JDK与Oracle profile环境配置
1.LINUX JDK环境配置 #set java environment export JAVA_HOME=/usr/java/jdk1.7.0_79 export JRE_HOME=/usr/ja ...
- Jquery中click事件重复执行的问题
平常没注意事件绑定问题,在此注意一下: function testClick(obj){ $("select").off().on("click", funct ...
- 微信开发创业交流QQ群列表
方倍工作室参与或主导的微信相关的QQ社群列表,欢迎对照加入. QQ群号 群名称 说明 推荐 518924126 微信平台开发有问必答 知识付费 ★★★★★ 188280503 微信公众平台开发最佳实 ...
- json 不能 dumps Decimal 解决办法
class DecimalEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, decimal.Decimal): ret ...
- Win10 x64 + CUDA 10.0 + cuDNN v7.5 + TensorFlow GPU 1.13 安装指南
Win10 x64 + CUDA 10.0 + cuDNN v7.5 + TensorFlow GPU 1.13 安装指南 Update : 2019.03.08 0. 环境说明 硬件:Ryzen R ...
- UBUNTU18.04安装网易云音乐并直接图标启动
这是一个网友改的程序,安装好以后把 ~/.cache/netcase-cloud-music 这个目录删除掉,就可以正常使用了,不用root权限
- luogu P3193 [HNOI2008]GT考试
传送门 单串匹配显然用\(kmp\) 一个暴力的dp是设\(f_{i,j}\),表示前\(i\)位,正在匹配给定串第\(j\)位的方案,转移就枚举下一位放什么,然后使用\(kmp\)看会匹配到给定串的 ...