Java8 Stream写给自己的小白流式操作

简介: Java8 Stream写给自己的小白流式操作

在这里插入图片描述

Java8 的出现可以说是 Java 面向函数式编程前进的一大步。刨除了很多冗余操作,间接的解放了双手👋

在看本篇文章时如果对 Java8的函数式接口不了解可以移步 Java8 Functional Interface写给自己的小白函数式接口说明

Java8 Stream 采用的是函数式编程方式,使用函数式编程方式在集合类上进行复杂操作的工具,可以更容易的使用 Lambda 表达式的形式书写,就看下流式操作正确的打开方式

对于不太清楚Stream流式操作的朋友而言,可以尽情 XX 了 😁
另附一张归结的思维导图,可以查看是否有自己需要的 API

在这里插入图片描述

1 流的创建

创建流的方式有很多种,比如:Arrays.streamStream.ofCollection.streamStream.iterateStream.generate,流的创建方式有很多,比较常用的则是根据 Collection.stream(),从集合中创建流

static <T> Stream<T> stream(T[] array)
    
static<T> Stream<T> of(T... values)
    
default Stream<E> stream()
    
static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
    
static<T> Stream<T> generate(Supplier<T> s)
第一种stream方法是 Arrays.stream | 第三种是 Collection.stream

1.1 创建流方式示例

Arrays.stream 通过 Arrays 的静态方法来进行流的创建,参数为一个范型数组

String[] strArray = { "龙台", "百凯", "英雄" };
Arrays.stream(strArray).forEach(System.out::println);
// 顺序输出  龙台  百凯  英雄

Stream.of 传入一个泛型数组,或者多个参数,创建一个流,底层也是调用了 Arraysstream 静态方法

String[] strArray = { "龙台", "百凯", "英雄" };
Stream.of(strArray).forEach(System.out::println);
// 顺序输出  龙台  百凯  英雄

Collection.stream 可以使用集合的接口的默认方法。使用这个方法,包括继承 Collection 的接口,如:Set,List,Map,SortedSet 等等都可以创建流

List<String> strList = Arrays.asList("龙台", "百凯","英雄");
strList.stream().forEach(System.out::println);
// 顺序输出  龙台  百凯  英雄

Stream.iterate 该方式是在 Stream 接口下的静态方法,见名识义可以看出是以迭代器的方式创建一个流

Stream.iterate(1, each -> each + 1)
        .limit(3)
        .collect(Collectors.toList())
        .forEach(System.out::println);
// 顺序输出 1 2 3

Stream.generate 同样是 Stream 接口下的静态方法,参数就是一个 Supplier 的供给型的参数

Stream.generate(Math::random)
        .limit(5)
        .forEach(System.out::println);
// 依次打印5个随机数

2 Stream 流的处理顺序

Stream 流的系列流程可以总结分为以下操作

数据源(source) -> 数据转换/处理(中间操作) -> 结果处理(终端操作)
数据源指的就是通过以上几种方式生成的 Stream 流。需要注意的是: 终端操作一旦执行,那么整个流的生命周期就结束了,无法再进行计算或再次汇总

❗️❗️❗️之前我也认为,会顺序输出 龙台 百凯两个字符串,但是实际上是一个也不会输出,因为当前代码中 没有包含终端操作

Stream.of("龙台", "百凯")
        .filter(each -> {
            System.out.println(each);
            return true;
        });

如果说在👆代码加以点缀,那么就变得不一样了

Stream.of("龙台", "百凯")
        .filter(each -> {
            System.out.println("filter:" + each);
            return true;
        })
        .forEach(each -> System.out.println("forEach:" + each));
    }
filter:龙台
forEach:龙台
filter:百凯
forEach:百凯
之前一直认为是执行完 filter 操作,然后才会执行接下来的 forEach 👋👋👋的打脸

Stream 包含中间操作,这个在使用上也是一个坑,那就是流的 延迟性,也叫做 惰性操作
实际上在处理 龙台 元素的时候,执行完 filter 就会执行 forEach ,然后继续执行 百凯 元素

查阅资料说明 这种设计的方式是出于性能考虑

Stream.of("龙台", "百凯", "英雄")
        .map(each -> {
            System.out.println("map: " + each);
            return each;
        })
        .anyMatch(each -> {
            System.out.println("anyMatch: " + each);
            return each.startsWith("百"); // 过滤以 '百' 开头的元素
        });
    }
map: 龙台
anyMatch: 龙台
map: 百凯
anyMatch: 百凯

终端操作 anyMatch() 表示如果发现以 '百' 开头的元素,停止执行直接返回,所以 '英雄' 元素就没有被循环 ♻️ 执行

如果仅仅是上面的方式可能无法直观的看出性能差距在哪,接下来以 mapfilter 的方式来进行操作

Stream.of("龙台", "百凯", "驷马", "狂妄", "偏执", "英雄")
        .map(each -> {
            System.out.println("map: " + each);
            return each;
        })
        .filter(each -> {
            System.out.println("filter: " + each);
            return each.startsWith("英");
        })
        .forEach(each -> System.out.println("forEach: " + each));
map: 龙台
filter: 龙台
map: 百凯
filter: 百凯
map: 驷马
filter: 驷马
map: 狂妄
filter: 狂妄
map: 偏执
filter: 偏执
map: 英雄
filter: 英雄
forEach: 英雄

通过打印得知,执行顺序分别是一次 mapfilter ,如果将 filtermap 的执行顺序转换一下的话

Stream.of("龙台", "百凯", "驷马", "狂妄", "偏执", "英雄")
        .filter(each -> {
            System.out.println("filter: " + each);
            return each.startsWith("英");
        })
        .map(each -> {
            System.out.println("map: " + each);
            return each;
        })
        .forEach(each -> System.out.println("forEach: " + each));
filter: 龙台
filter: 百凯
filter: 驷马
filter: 狂妄
filter: 偏执
filter: 英雄
map: 英雄
forEach: 英雄
根据 Stream 的执行方式来正确的定义函数API的顺序,如果集合包含大量元素的情况下,这种性能提升还是很可观的
值得一提的是 Streamsorted 函数是水平执行的,会首先将集合中的内容全部在 sorted 方法中执行后才会继续向下执行

3 数据处理/转换

一个流在被创建后,可以使用多个数据处理、转换操作,全部操作之后返回给终端操作的是一个新的流

3.1 流的映射 map/flatMap

在日常工作中通常会遇到这个场景,需要将一个集合对象中的某个属性提取出来重新组合成为一个新的集合, Stream 中的 mapflatMap 可以很好的完成这个需求,关键是:代码还很简洁 ❤️
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
map() 将学生集合中的学生名称抽取映射成为一个新的只包含名称的集合
@Data
public class Student {

    private String num, name, area;

    public Student(String num, String name, String area) {
        this.area = area; this.name = name; this.num = num;
    }

    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("001", "东天北","山东"),
                new Student("002", "心语祺","北京"),
                new Student("003", "似无望","河南"));
        
        List<String> nameList = studentList.stream()
                .map(Student::getName)
                .collect(Collectors.toList());
    }
}
在这里先说明下 mapflatMap 的区别 ⬇️
flatmap 可以将一个二维的集合映射成一个一维,相当于他映射的深度比 map 深了一层
Stream<List<Integer>> inputStream = Stream.of(
        Arrays.asList(1),
        Arrays.asList(2, 3),
        Arrays.asList(4, 5, 6)
);
List<Integer> collect = inputStream
        .flatMap(each -> each.stream())
        .collect(Collectors.toList());
collect.forEach(System.out::println);
对于流映射还包括 mapToDoublemapToIntmapToLong 基本上相同的功能,有需求可以自己试下

3.2 流的过滤 filter

使用 filter 进行数据筛选,挑选出符合程序要求的元素
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6,7,8);
integerList.stream().filter(each -> each > 6).forEach(System.out::println);
// filter条件 流中的元素必须 大于 6,符合要求的只有7和8 所以会被打印

3.3 流的去重 distinct

使用 distinct 对数据流中的元素进行去重操作
在示例代码中, 偏执 出现两次,在调用 distinct 之后就会被去除
List<String> stringList = Arrays.asList("狂妄", "偏执", "英雄", "偏执");
stringList.stream().distinct().forEach(System.out::println);

3.4 流的排序 sorted

Stream 中对于排序使用的是 sorted 对比与普通的集合、数组排序, Stream 的排序更会有优势,可以通过链式调用过滤、去重等方法筛选出结果数据流再进行排序,会更方便一些
@Data
public class Student {

    private String num, name, area;

    public Student(String num, String name, String area) {
        this.area = area; this.name = name; this.num = num;
    }

    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("003", "英雄","河南"),
                new Student("002", "偏执","北京"),
                new Student("001", "狂妄","山东"));

        List<Student> students = studentList.stream()
                .sorted(((s1, s2) -> s1.getNum().compareTo(s2.getNum())))
                .collect(Collectors.toList());
        students.forEach(each -> System.out.println(each.toString()));
    }
}
Student(num=001, name=狂妄, area=山东)
Student(num=002, name=偏执, area=北京)
Student(num=003, name=英雄, area=河南)

3.5 执行流 peek

为什么要叫 peek 为执行流呢,之前我也是分不清这个有什么用,看了下他的接受参数就明白了 😄
peek 入参是一个 Consumer 所以只会执行消费,而不会像 map 一样将执行后的内容返回
那与 forEach 的不同点在于 一个为 中间操作 一个为 终端操作
Stream<T> peek(Consumer<? super T> action);
peek 真的只是执行一下,再没有别的作用了,数据在执行 peek 前和执行后 无差别
Stream.of("one", "two", "three", "four")
        .filter(each -> each.length() > 3)
        .peek(each -> System.out.println("peek:" + each))
        .map(each -> "map:" + each)
        .forEach(System.out::println);

3.6 截取和刨除 limit / skip

limit 和 数据库的分页道理是一致的,而 skip 则是反其道而行。通过代码来进行查看
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6);
integerList.stream().limit(3).forEach(System.out::println);
// 打印 1 2 3 
integerList.stream().skip(3).forEach(System.out::println);
// 打印 4 5 6

4 流的终端操作

当流使用了终端操作之后,那么这个流的生命周期即为结束,如果再进行调用会编译异常或报错处理。在终端操作中会是对流的遍历操作

4.1 流的分组聚合 groupingBy / partitioningBy

流的聚合 groupingBymysql 数据库中的 Group By 原理基本一致
List<Student> studentList = Arrays.asList(
        new Student("003", "英雄","山东"),
        new Student("002", "偏执","北京"),
        new Student("001", "狂妄","山东"));

Map<String, List<Student>> stuGroup = studentList.stream()
        .collect(Collectors.groupingBy(each -> each.getArea()));

stuGroup.forEach((key, val) -> System.out.println(key + ":" + val.toString()));

山东:[Student(num=003, name=英雄, area=山东), Student(num=001, name=狂妄, area=山东)]
北京:[Student(num=002, name=偏执, area=北京)]
partitioningBy 则和普通 Group By 显的有一丝不一致。详情查看代码
可以看的到 在进行分组时,返回参数的 keyBoolean 类型,在执行 partitioningBy 时,如果地区为 “山东”则为 True
List<Student> studentList = Arrays.asList(
        new Student("003", "英雄","山东"),
        new Student("002", "偏执","北京"),
        new Student("001", "狂妄","山东"));

Map<Boolean, List<Student>> stuGroup = studentList.stream()
        .collect(Collectors.partitioningBy(each -> Objects.equals(each.getArea(), "山东")));

System.out.println("结果为 true :" + stuGroup.get(true));
System.out.println("结果为 false :" + stuGroup.get(false));

结果为 true :[Student(num=003, name=英雄, area=山东), Student(num=001, name=狂妄, area=山东)]
结果为 false :[Student(num=002, name=偏执, area=北京)]

4.2 流的循环 forEach

forEach 接受一个 Consumer 类型的消费函数 无返回,其实就是之前写的 for、forEach 循环
void forEach(Consumer<? super T> action);

4.3 流的汇总 collect

一般而言,我们在使用 Streamcollect 方法时都是将流元素 转换为指定类型的集合
List<Integer> integerList = Arrays.asList(1,2,3,4,5).stream()
        .collect(Collectors.toList());

5 short-circuiting

5.1 流数据的获取 findFirst

findFirst 通过API名称可以看出:获取的是流的第一个元素,返回值是一个 Optional,如果不清楚 Optional 应用的可以参考博文 如何正确的使用Java 8的新特性之 Optional
// 如果流获取第一个元素为空,那么就会返回默认值
Optional<String> optional = Arrays.asList("偏执","狂妄","英雄").stream().findFirst();
System.out.println(optional.orElse("默认返回"));

5.2 流的匹配 anyMatch / allMatch / noneMatch

anyMatch 任何一个元素匹配,返回 true
// 毫无疑问 当执行到元素 4 时,返回 **`true`**
boolean anyMatchResult = Arrays.asList(1,2,3,4,5).stream().anyMatch(each -> each > 3);
allMatch 所有元素匹配,返回 true
// 因为不满足所有元素 > 3 所以返回值为false
boolean allMatchResult = Arrays.asList(1,2,3,4,5).stream().anyMatch(each -> each > 3);
noneMatch 没有一个元素匹配,返回 true
boolean noneMatchResult = Arrays.asList(1,2,3,4,5).stream().anyMatch(each -> each > 10);

6 流的规约操作

6.1 流统计 summaryStatistics

流的统计这个功能在进行集合数据统计上来说是非常有用的,可以求平均值、最大值、最小值、个数、数据和
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6);
IntSummaryStatistics iss = integerList.stream().mapToInt(each -> each).summaryStatistics();
iss.getMin(); iss.getAverage(); iss.getCount(); iss.getMax(); iss.getSum();

6.2 流统计 average、count、max、min、sum

average、count、max、min、sum 其实就相当与 summaryStatistics 拆出的个体 一笔带过了
Stream<Integer> stream = Stream.of(1,2,3,4,5);
OptionalInt max = stream.mapToInt(each -> each).max();
OptionalLong min = stream.mapToLong(each -> each).min();
OptionalDouble average = stream.mapToInt(each -> each).average();
long count = stream.mapToInt(each -> each).count();
int sum = stream.mapToInt(each -> each).sum();

6.3 流的统计 count / counting

// 二者输出都为 3
List<String> stringList = Arrays.asList("偏执","狂妄","英雄");
long count = stringList.stream().count();
long counting = stringList.stream().collect(Collectors.counting());

7 总结

关于 Stream 的内容大致就总结这么多吧,基本上一些常用项都已概括在本篇中
接下来会总结下并行流 parallelStream 提供了流的并行处理,底层是使用了 Fork/Join 框架实现
相关文章
|
25天前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
41 6
|
26天前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
16天前
|
Rust 安全 Java
Java Stream 使用指南
本文介绍了Java中Stream流的使用方法,包括如何创建Stream流、中间操作(如map、filter、sorted等)和终结操作(如collect、forEach等)。此外,还讲解了并行流的概念及其可能带来的线程安全问题,并给出了示例代码。
|
27天前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
26 0
|
1月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
2月前
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
46 1
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
2月前
|
Java Shell 流计算
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
26 1
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
|
2月前
|
存储 Java 数据处理
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
43 1
|
3月前
|
SQL Java Linux
Java 8 API添加了一个新的抽象称为流Stream
Java 8 API添加了一个新的抽象称为流Stream
|
2月前
|
Java API 数据处理
java Stream详解
【10月更文挑战第4天】
41 0