关于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. Java ExecutorService四种线程池的例子与说明

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...

  2. linux tail -f messages查看控制台失败

    [root@localhost log]# tail -f /var/log/messages ......................... tail: cannot watch `/var/l ...

  3. netty基础篇

    什么是Bio? 当客户端数量过多时,创建的线程会越来越多,最终服务挂掉,因为客户端的线程数量和服务端创建的线程数量是一一对应的. 什么是伪异步IO? 什么是Nio? 什么是Aio

  4. js 数组与字符串的相互转化

    数组转字符串:join() 字符串转数组:split('')

  5. go函数练习

    1.编写程序,在终端输出九九乘法表. package main import ( "fmt" ) func main() { for i := 1; i <= 9; i++ ...

  6. idea常用快捷键及自定义快捷键汇总

    以下都是挨个进行验证过的 生成get和set方法为:alt+insert 自动补全返回值,自动补全变量名称和属性名称:ctrl+alt+v 输入System.out.println()的快捷方法是:输 ...

  7. nginx 全局配置

    nginx 全局配置 #user nobody; # user 主模块 ,指定nginx worker 进程的运行用户组 worker_processes ; # worker_processes 开 ...

  8. HDU 2522 A simple problem (模拟)

    题目链接 Problem Description Zty很痴迷数学问题..一天,yifenfei出了个数学题想难倒他,让他回答1 / n.但Zty却回答不了^_^. 请大家编程帮助他. Input 第 ...

  9. bzoj 4184: shallot (线段树维护线性基)

    题面 \(solution:\) 这一题绝对算的上是一道经典的例题,它向我们诠释了一种新的线段树维护方式(神犇可以跳过了).像这一类需要加入又需要维护删除的问题,我们曾经是遇到过的像莫对,线段树... ...

  10. mysql 原理 ~ checkpoint

    一 简介:今天咱们来聊聊checkpoint 二 定义: checkpoin是重做日志对数据页刷新到磁盘的操作做的检查点,通过LSN号保存记录,作用是当发生宕机等crash情况时,再次启动时会查询ch ...