java8新特性——轻松玩转stream流的数据操作(串行)

本文深入探讨了Java8的Stream API,包括流的创建、中间操作(无状态与有状态)、终止操作及其各种用法,如过滤、映射、切片、去重、排序、规约、收集等。示例代码详细展示了如何使用Stream API进行数据处理,帮助读者掌握这一强大的数据处理工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java8新特性专栏:https://blog.youkuaiyun.com/caoyuan666/category_11801993.html?spm=1001.2014.3001.5482

0.总体介绍

0.1 stream流的概念

  • Stream API 提供了一种高效且易于使用的处理数据的方式。Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作, 就类似于使用 SQL 执行的数据库查询。
  • java中的流都是用于传输数据的数据渠道,但是不同的流具体功能不同,就像java中的IO流类似,stream流也是用于承载数据传输,但是stream流是为了处理数据。
  • 既然stream流的核心目的是处理数据,那么对于无需前后数据就可处理的过程可利用多线程操作,本文主要讲stream的串行操作,并行操作放在了下一篇博客中。

0.2 steam流的三部分

  • 创建 Stream:一个数据源(如: 集合、 数组), 获取一个流
  • 中间操作:一个中间操作链, 对数据源的数据进行处理
  • 终止操作(终端操作):一个终止操作, 执行中间操作链, 并产生结果

0.3 注意事项

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。 相反, 他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。 这意味着他们会等到需要结果的时候才执行。

1. 创建方式

四种构造方法:

    @Test
    public void test7() {
        // 1.通过Collection集合的方法获取
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        // 2.数组:通过Arrays的静态方法stream()
        String[] strArr = new String[2];
        Stream<String> stream1 = Arrays.stream(strArr);

        // 3.通过Stream的静态方法of()
        Stream<String> a = Stream.of("a", "b", "c");

        // 4.创建无限流
        // 4.1 迭代
        Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 1);
        stream2.limit(5).forEach(System.out::print);
        System.out.println();

        // 4.2 生成
        Stream.generate(Math::random)
                .limit(5)
                .forEach(System.out::println);
    }

前3种应该比较好理解,个人认为最常见的还是第一种,对于无限流,创建时写入创建元素的方法,那么流的长度就是无穷大,为了功能需求,一般通过中间操作limit()来实现限制流的长度

  • 迭代:Stream.iterate() 第一个参数为第一个元素大小,第二个参数为后一个元素在前一个元素已知条件下的生成规则
  • 生成:生成方法

2.中间操作

位置类型函数作用
中间操作无状态filter()过滤
中间操作无状态peek()流中的执行操作,debug,不改变元素内容
中间操作无状态map()元素->元素 映射
中间操作无状态FlatMap()元素->流 映射,并将流中的元素合并到原数据流中
中间操作无状态mapToInt()元素->IntStream 映射
中间操作无状态mapToDouble()元素->DoubleStream 映射
中间操作无状态mapToLong()元素->LongStream 映射
中间操作无状态flatMapToInt()元素->IntStream 映射,和原数据流合并
中间操作无状态flatMapToDouble()元素->DoubleStream 映射,和原数据流合并
中间操作无状态flatMapToLong()元素->LongStream 映射,和原数据流合并
中间操作有状态limit(n)切片:保留前n个元素
中间操作有状态skip(n)切片:丢弃前n个元素
中间操作有状态distinct()筛选:去重,hashCode()&equals()
中间操作有状态sorted()排序
中间操作有状态sorted(comp)按比较器comp排序
  • 无状态: 指元素的处理不受之前元素的影响,各个元素的处理可多线程操作
  • 有状态: 指该操作只有拿到所有元素之后才能继续下去。

下面的程序主要用下面这个流进行举例:

        Stream<String> stream1 = Stream.iterate("a", (x) -> {
            char[] chars = x.toCharArray();
            return x + (char) (chars[chars.length - 1] + 1);
        }).limit(6);

        stream1.forEach(System.out::println);

输出:

a
ab
abc
abcd
abcde
abcdef

2.1无状态操作

2.1.1 过滤

  • filter(Predicate p) 接收 Lambda , 从流中保留符合条件的元素。
stream1.filter(x -> x.length() > 3).forEach(System.out::println);

结果:

abcd
abcde
abcdef

2.1.2 执行

执行操作主要用于debug中使用,和forEach作用类似,对每个元素遍历操作且不改变元素内容,但forEach是结束流只能在最后用一次,所以中间的操作使用peek()

  • peek(Consumer<? super T> action) 用于流中间不改变元素内容的操作
    为更好的举例,这里使用int类型的流举例,可以看出流中执行的顺序:
        List<Integer> list = Arrays.asList(4, 7, 9, 11, 12);
        list.stream()
                .peek(x -> System.out.println("stream: " + x))
                .map(x -> x + 2)
                .peek(x -> System.out.println("map: " + x))
                .filter(x -> x % 2 != 0)
                .peek(x -> System.out.println("filter: " + x))
                .limit(2)
                .peek(x -> System.out.println("limit: " + x))
                .collect(Collectors.toList());
stream: 4
map: 6
stream: 7
map: 9
filter: 9
limit: 9
stream: 9
map: 11
filter: 11
limit: 11

Process finished with exit code 0

2.1.3 映射

  • map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap(Function f) 接收一个函数作为参数, 将流中的每个值都换成另一个流, 然后把所有流连接成一个流
  • mapToDouble(ToDoubleFunction f) 接收一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的 DoubleStream。
  • mapToInt(ToIntFunction f) 接收一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的 IntStream。
  • mapToLong(ToLongFunction f) 接收一个函数作为参数, 该函数会被应用到每个元素上, 产生一个新的 LongStream。

map:用于数据处理,这里相当于大数据map-reduce中map处理,返回类型可以与输入类型不同,在结束操作中有reduce方法

stream1.map(x -> x+"111").forEach(System.out::println);
a111
ab111
abc111
abcd111
abcde111
abcdef111

flatMap:在flatMap中,返回的数据类型是一个stream流类型,这个方法将流中的每个元素打平到原来的大流中,类似于集合的addAll()方法

        stream1.flatMap(x -> {
            char[] chars = x.toCharArray();
            Character[] newChars = new Character[chars.length];
            for (int i = 0; i < chars.length; i++) {
                newChars[i] = chars[i];
            }
            Stream<Character> stream2 = Arrays.stream(newChars);
            return stream2;
        }).forEach(System.out::print);
aababcabcdabcdeabcdef

2.2 有状态操作

有状态操作需要将所用元素执行完前面的操作后,再一起执行

2.2.1 切片

  • limit(long maxSize) 截断流, 使其元素不超过给定数量。
  • skip(long n) 跳过元素, 返回一个扔掉了前 n 个元素的流。 若流中元素不足 n 个, 则返回一个空流。 与 limit(n) 互补

2.2.2 筛选去重

  • distinct() 筛选, 通过流所生成元素的 hashCode() 和 equals() 去除重复元素

2.2.3 排序

  • sorted() 产生一个新流, 其中按自然顺序排序
  • sorted(Comparator comp) 产生一个新流, 其中按比较器顺序排序

3.终止操作(结束操作)

位置类型函数作用
结束操作短路操作findFirst()返回第一个元素
结束操作短路操作findAny()返回任意一个元素
结束操作短路操作allMatch()是否匹配所有元素
结束操作短路操作anyMatch()是否至少匹配一个元素
结束操作短路操作noneMatch()是否没有匹配所有元素
结束操作短路操作noneMatch()是否没有匹配所有元素
结束操作非短路操作reduce()规约合并到一个元素
结束操作非短路操作collect()收集到其他形式
结束操作非短路操作forEach()内部迭代
结束操作非短路操作toArray()转化为数组
结束操作非短路操作max()统计:最大值
结束操作非短路操作min()统计:最小值
结束操作非短路操作count()统计:元素数量
  • 非短路操作: 指必须处理所有元素才能得到最终结果;
  • 短路操作: 指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。

程序示例:对于结束操作,涉及到大量属性特征的处理,所以创建了一个book类,如下所示,类型包含:学习&小说

class Book{
    private String name;
    private int page;
    private Type type;

    public Book() {
    }

    public Book(String name, int page, Type type) {
        this.name = name;
        this.page = page;
        this.type = type;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", page=" + page +
                ", type=" + type +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return page == book.page && Objects.equals(name, book.name) && type == book.type;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, page, type);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }
}

enum Type {
    STUDY,
    NOVEL
}

使用的流如下所示:

    @Test
    public void test10(){
        List<Book> books = new ArrayList<>();
        books.add(new Book("Spring", 200, Type.STUDY));
        books.add(new Book("Python", 180, Type.STUDY));
        books.add(new Book("C++", 230, Type.STUDY));
        books.add(new Book("NOVEL1", 230, Type.NOVEL));
        books.add(new Book("NOVEL2", 280, Type.NOVEL));
        books.add(new Book("NOVEL3", 180, Type.NOVEL));

        books.stream().forEach(System.out::println);
    }
Book{name='Spring', page=200, type=STUDY}
Book{name='Python', page=180, type=STUDY}
Book{name='C++', page=230, type=STUDY}
Book{name='NOVEL1', page=230, type=NOVEL}
Book{name='NOVEL2', page=280, type=NOVEL}
Book{name='NOVEL3', page=180, type=NOVEL}

3.1 短路操作

短路操作再遇到某些符合条件的元素时可提前结束,得到结果,无需遍历所有元素

3.1.1 查找

  • findFirst() 返回第一个元素
  • findAny() 返回当前流中的任意元素

3.1.2 匹配

  • allMatch(Predicate p) 检查是否匹配所有元素
  • anyMatch(Predicate p) 检查是否至少匹配一个元素
  • noneMatch(Predicate p) 检查是否没有匹配所有元素
        // 1.查找
        Optional<Book> first = books.stream().findFirst();
        System.out.println(first.get());

        Optional<Book> any = books.stream().findAny();
        System.out.println(any.get());

        // 2.匹配
        boolean b = books.stream().allMatch(x -> x.getPage() > 200);
        System.out.println(b);

        boolean b1 = books.stream().anyMatch(x -> x.getType().equals(Type.STUDY));
        System.out.println(b1);

        boolean b2 = books.stream().noneMatch(x -> x.getPage() > 200);
        boolean b3 = books.stream().noneMatch(x -> x.getPage() < 100);
        System.out.println(b2 + "  " + b3);
Book{name='Spring', page=200, type=STUDY}
Book{name='Spring', page=200, type=STUDY}
false
true
false  true

3.2 非短路操作

3.2.1 规约

规约reduce,相当于大数据map-reduce模型中的reduce,将之前map之后各个元素结果结合到一起

  • reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值。返回 T。iden为初始元素,b为二元运算,结合各个元素,由于有初始元素iden,所以即使流为空,也可以返回iden
  • reduce(BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值。返回 Optional
        Integer allPage = books.stream().map(Book::getPage).reduce(100, (a, b) -> a + b);
        System.out.println(allPage);

        List<Book> books2 = new ArrayList<>();
        Integer allPage1 = books2.stream().map(Book::getPage).reduce(200, (a, b) -> a + b);
        System.out.println(allPage1);

        Optional<Integer> allPage2 = books.stream().map(Book::getPage).reduce((a, b) -> a + b);
        System.out.println(allPage2.get());
1400
200
1300

3.2.2 收集

  • collect(Collector c)将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
  • Collectors实用类提供了很多静态方法, 可以方便地创建常见Collector接口的收集器实例

Collectors的常用静态方法:

  1. 收集为Collection集合
    1. toList(),将流收集到List,返回类型为List
    2. toSet(),把流中元素收集到Set,返回类型为Set
    3. toCollection(Supplier sup),把流中元素收集到创建的集合,可用于任何其他的集合类型
  2. 收集到统计特征
    1. counting():计算流中元素的个数,返回Long
    2. summingInt(Function fun):对流中元素的整数属性求和,返回Integer
    3. averagingInt(Function fun):计算流中元素Integer属性的平均值,返回Double
    4. summarizingInt(Function fun):收集流中Integer属性的统计值。如: 平均值,返回IntSummaryStatistics
  3. 连接符:joining(),连接流中每个字符串,返回String
  4. 比较器统计
    1. maxBy(Comparator com):根据比较器选择最大值,返回Optional
    2. minBy(Comparator com):根据比较器选择最小值,返回Optional
  5. 规约:reducing():从一个作为累加器的初始值开始, 利用BinaryOperator与流中元素逐个结合, 从而归约成单个值
  6. 分组&分区
    1. 分组groupingBy(Function fun):根据某属性值对流分组, 属性为K, 结果为V,返回Map<K, List>,可以级联实现多级分组
    2. 分区:partitioningBy(Function fun),根据true或false进行分区,Map<Boolean, List>
  7. collectingAndThen():包裹另一个收集器, 对其结s果转换函数
        // 1.集合
        List<Book> collect = books.stream().limit(3).collect(Collectors.toList());
        System.out.println(collect);

        Set<Book> collect1 = books.stream().limit(3).collect(Collectors.toSet());
        System.out.println(collect1);

        ArrayList<Book> collect2 = books.stream().limit(3).collect(Collectors.toCollection(ArrayList::new));
        System.out.println(collect2);

        // 2.统计
        Long collect3 = books.stream().limit(3).collect(Collectors.counting());
        System.out.println(collect3);

        IntSummaryStatistics collect4 = books.stream().limit(3).collect(Collectors.summarizingInt(Book::getPage));
        System.out.println(collect4.getMax());
        System.out.println(collect4.getSum());
        System.out.println(collect4.getAverage());

        // 3.连接
        String collect5 = books.stream().map(Book::getName).collect(Collectors.joining(",", "+++", "---"));
        System.out.println(collect5);

        // 4.分组
        Map<Type, List<Book>> collect6 = books.stream().collect(Collectors.groupingBy(Book::getType));
        System.out.println(collect6);

        Map<Type, Map<Boolean, List<Book>>> collect7 = books.stream()
                .collect(Collectors.groupingBy(Book::getType, Collectors.groupingBy(x -> x.getPage() > 200)));
        System.out.println(collect7);
[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}]
[Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}, Book{name='Spring', page=200, type=STUDY}]
[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}]
3
230
610
203.33333333333334
+++Spring,Python,C++,NOVEL1,NOVEL2,NOVEL3---
{NOVEL=[Book{name='NOVEL1', page=230, type=NOVEL}, Book{name='NOVEL2', page=280, type=NOVEL}, Book{name='NOVEL3', page=180, type=NOVEL}], STUDY=[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}, Book{name='C++', page=230, type=STUDY}]}
{NOVEL={false=[Book{name='NOVEL3', page=180, type=NOVEL}], true=[Book{name='NOVEL1', page=230, type=NOVEL}, Book{name='NOVEL2', page=280, type=NOVEL}]}, STUDY={false=[Book{name='Spring', page=200, type=STUDY}, Book{name='Python', page=180, type=STUDY}], true=[Book{name='C++', page=230, type=STUDY}]}}

Process finished with exit code 0

3.2.3 迭代

  • forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。 相反, Stream API 使用内部迭代——它帮你把迭代做了)

3.2.4 数组

  • toArray():转为数组
        Object[] objects = books.stream().map(Book::getName).toArray();
        System.out.println(Arrays.toString(objects));
[Spring, Python, C++, NOVEL1, NOVEL2, NOVEL3]

3.2.5 统计

  • count() 返回流中元素总数
  • max(Comparator c) 返回流中最大值
  • min(Comparator c) 返回流中最小值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力改掉拖延症的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值