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

java8新特性专栏:https://blog.csdn.net/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) 返回流中最小值

猜你喜欢

转载自blog.csdn.net/caoyuan666/article/details/124610824