java8(三)什么是流?函数式数据处理神器 一、java7与java8中实现方式对比 二、如何理解流? 三、流和集合有哪些不同? 四、流的操作

流是Java API的新成员,以声明的方式处理数据合集。就现在来说,你可以把它们看成遍历数据集的高级迭代器。可以透明地并行处理,你无需写任何多线程代码了。

一、java7与java8中实现方式对比

我们首先举个例子,看一下在java7和java8当中,引入Stream Api后,究竟发生了哪些变化?

实现一个从菜单筛选低热量(小于500)菜肴名称并从小到大排序的功能:

1.1 java7实现

import lombok.Data;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * @description: 实现一个从菜单筛选低热量(小于500)菜肴名称并从小到大排序的功能
 * @author:weirx
 * @date:2021/10/20 15:00
 * @version:3.0
 */
public class ChoiceCalorieByJava7 {

    @Data
    static class Dish {
        private String name;
        private Integer calorie;

        public Dish(String name, Integer calorie) {
            this.name = name;
            this.calorie = calorie;
        }
    }

    public static void main(String[] args) {
        List<Dish> dishes = Arrays.asList(
                new Dish("烧烤", 1000),
                new Dish("沙拉", 100),
                new Dish("汉堡", 1200),
                new Dish("火锅", 800),
                new Dish("粥", 300)
        );

        List<Dish> lowerCalorieDishes = new ArrayList<>();
        // 筛选小于500卡路里
        for (Dish dish : dishes) {
            if (dish.getCalorie() < 500) {
                lowerCalorieDishes.add(dish);
            }
        }
        // 排序
        lowerCalorieDishes.sort(new Comparator<Dish>() {
            @Override
            public int compare(Dish o1, Dish o2) {
                return o1.getCalorie().compareTo(o2.getCalorie());
            }
        });
        List<String> dishNames = new ArrayList<>();
        // 获取菜肴名称
        for (Dish dish : lowerCalorieDishes) {
            dishNames.add(dish.getName());
        }
        // 打印结果 [沙拉, 粥]
        System.out.println(dishNames);
    }
}

1.2 java8实现

import lombok.Data;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @description: 实现一个从菜单筛选低热量(小于500)菜肴名称并从小到大排序的功能
 * @author:weirx
 * @date:2021/10/20 15:00
 * @version:3.0
 */
public class ChoiceCalorieByJava8 {

    @Data
    static class Dish {
        private String name;
        private Integer calorie;

        public Dish(String name, Integer calorie) {
            this.name = name;
            this.calorie = calorie;
        }
    }

    public static void main(String[] args) {
        List<Dish> dishes = Arrays.asList(
                new Dish("烧烤", 1000),
                new Dish("沙拉", 100),
                new Dish("汉堡", 1200),
                new Dish("火锅", 800),
                new Dish("粥", 300)
        );

        List<String> dishNames = dishes.stream()
                .filter(dish -> dish.getCalorie() < 500) //筛选卡路里小于500
                .sorted(Comparator.comparing(Dish::getCalorie)) //排序
                .map(Dish::getName) //获取名称
                .collect(Collectors.toList()); //转成list
        // 打印结果 [沙拉, 粥]
        System.out.println(dishNames);
    }
}

相比之下在java7中繁杂的业务代码只需要一条流式的代码就完成了,并且将其中的stream()替换成parallelStream()就可以利用多核去执行这行代码。

1.3 总结

下面通过上面的例子,简单总结下使用Stream API的好处:

1)声明性:更简洁,更易读。体现事物本身,而非代码过程。
2)可复合:更灵活,减少大量的重复代码。
3)可并行:更好的性能。

二、如何理解流?

简短的定义就是:从支持数据处理操作的源生成的元素序列

下面简单介绍一下这句话的含义:

1)元素序列:像集合一样,流也提供一个接口,可以访问特定元素的一组有序值。集合(ArrayList、LinkedList等)关注数据,而流关注的是计算(filter 、 sorted 和 map等)。

2)源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。

3)数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执行,也可并行执行。

除此之外还有以下两个特点:

4)流水线:多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。流水线的操作可以看作对数据源进行数据库式查询。

5)内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

结合前面的例子简单说明下:

        List<String> dishNames = dishes.stream()
                .filter(dish -> dish.getCalorie() < 500) //筛选卡路里小于500
                .sorted(Comparator.comparing(Dish::getCalorie)) //排序
                .map(Dish::getName) //获取名称
                .collect(Collectors.toList()); //转成list

其中dishNames就是源,它给流提供一个元素序列,接下来,对流应用一系列数据处理操作:filter 、 sorted 和 map等。最后, collect 操作开始处理流水线,并返回结果(它和别的操作不一样,因为它返回的不是流,在这里是一个 List。

三、流和集合有哪些不同?

1)集合可以多次遍历,而流只能被遍历一次

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Stream<Integer> stream = list.stream();
        stream.forEach(System.out::print);
        stream.forEach(System.out::print);
    }

如上操作会报出以下异常:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at com.cloud.bssp.java8.stream.CollectionAndStream.main(CollectionAndStream.java:19)

2)遍历数据的方式不同
使用 Collection 接口需要用户去做迭代(比如用 foreach ),这称为外部迭代。 相反,Streams库使用内部迭代

相比于外部迭代,内部迭代的优势在于:
内部迭代时,项目可以透明地并行处理,或者用更优化的顺序进行处理。不需要像外部迭代手动去处理并行问题。

四、流的操作

Stream API定义了很多的操作,这些操作可以分为两大类:

1)filter、map、limit等可以连成一条流水线。这类操作也可以简称为中间操作。

诸如 filter 或 sorted 等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

2)collect触发流水线执行并关闭它。这类操作被称为终端操作。

终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如 List 、 Integer ,甚至 void 。

总而言之,流的使用一般包括三件事:
1)一个数据源(如集合)来执行一个查询;
2)一个中间操作链,形成一条流的流水线;
3)一个终端操作,执行流水线,并能生成结果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章