关于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理解的更多相关文章

  1. java中如何理解:其他类型 + string 与 自增类型转换和赋值类型转换

    java中如何理解:其他类型 + string 与 自增类型转换和赋值类型转换 一.字符串与其他类型连接 public class DemoString{ public static void mai ...

  2. Java中stream的详细用法

    来自于:Java 8 stream的详细用法_旅行者-CSDN博客_java stream 一.概述 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行 ...

  3. java中stream部分笔记

    Stream流表面上看起来与集合类似,允许你转换和检索数据.然而,两者却有显著的不同1.流不存储元素.它们存储在底层的集合或者按需生成2.流操作不改变他们的源数据.例如filter方法不会从一个新流中 ...

  4. Java中Stream流相关介绍

    什么是Stream? Stream是JDK8 API的新成员,它允许以声明性方式处理数据集合 特点 代码简洁: 函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环 多核友好 ...

  5. scala中Stream理解

    // Stream:Stream is lazy List; // Stream惰性求值指它只确定第一个值,后面的值用到再求值,这样可以防止数据过大全部加载导致内存溢出 // 将Range转化成Str ...

  6. 第四节:详细讲解Java中的类和面向对象思想

    前言 大家好,给大家带来详细讲解Java中的类和面向对象思想的概述,希望你们喜欢 类和面向对象 在Java中怎样理解对象,创建对象和引用:什么是引用,对于基础学习的同学,要深入了解引用.示例:Stri ...

  7. Java中的流(1)流简介

    简介 1.在java中stream代表一种数据流(源),java.io的底层数据元.(比作成水管)2.InputStream 比作进水管,水从里面流向你,你要接收,read3.OutputStream ...

  8. Java:泛型的理解

    本文源自参考<Think in Java>,多篇博文以及阅读源码的总结 前言 Java中的泛型每各人都在使用,但是它底层的实现方法是什么呢,为何要这样实现,这样实现的优缺点有哪些,怎么解决 ...

  9. 【转】输入/输出流 - 深入理解Java中的流 (Stream)

    基于流的数据读写,太抽象了,什么叫基于流,什么是流?Hadoop是Java语言写的,所以想理解好Hadoop的Streaming Data Access,还得从Java流机制入手.流机制也是JAVA及 ...

随机推荐

  1. 关于checkbox全选与全不选的实现与遇到的问题

    HTML: <div class="outbox"> <label for="box">全选</label> <inp ...

  2. Selenium基于Python 进行 web 自动化测试

    配置使用环境 下载相应的浏览器驱动, Firefox 是默认的 本文以 chrome 为主 ,放在scripts目录下ChromeDriver 官方下载地址 : 所有版本的 ChromeDriver ...

  3. spring @Transactional注解参数详解

    事物注解方式: @Transactional 当标于类前时, 标示类中所有方法都进行事物处理 , 例子: @Transactional public class TestServiceBean imp ...

  4. 网络地址转换-NAT

    网络地址转换-NAT 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.NAT组网和常用术语 私网:局域网内IP 公网:因特网的公网ip地址 NAT设备:就是讲私网地址转换为公网的 ...

  5. 函数和常用模块【day05】:装饰器高潮(三)

    本节内容 1.概述 2.装饰器定义 3.装饰器定义 4.带参数的生成器 一.概述 我们之前介绍了大幅片的内容,感觉跟装饰器半毛钱关系都没有,其实不然,我们分别详细阐述了高阶函数和内置函数,下面我们就来 ...

  6. Hbase记录-HBase基本操作(二)

    HBase Exists   可以使用exists命令验证表的存在.下面的示例演示了如何使用这个命令. hbase(main):024:0> exists 'emp' Table emp doe ...

  7. android measure的时候报空指针

    1.使用listview的时候,在代码中动态设置其高度,在android低版本中,这个低版本是以4.4为界,会报measure的空指针,原因是低版本relativelayout有个bug,使用list ...

  8. C#中使用Application.AddMessageFilter(this)要手动释放

    如题,要使用Application.RemoveMessageFilter(this);释放,如果不释放会造成很严重的内存泄漏.

  9. DotNetBar TreeGx用法

    添加一个节点和4个子节点treeGXHelp.Nodes[].Text = textBoxDropDownHelp.Text + "的主题"; treeGXHelp.Nodes[] ...

  10. C# 窗体内有子控件时鼠标检测

    public partial class FormPop : Form { public FormPop() { InitializeComponent(); } private void FormP ...