当前位置 > it书童 > java > 正文

流式编程开场

java it书童 2021-01-14 15:59:59 0赞 0踩 168阅读 0评论

筛选购物车商品-引出函数式接口妙用 继续以购物车商品筛选为例,现在小丽提出了这样的需求:

1.想看看购物车中都有什么商品

2.图书类商品都买

3.其余的商品中买两件最贵的

4.只需要给出两件商品的名称和总价

用传统的遍历数组方式实现如下:

package stream;

import com.alibaba.fastjson.JSON;
import lambda.cart.CartService;
import lambda.cart.Sku;
import lambda.cart.SkuCategoryEnum;
import org.junit.Test;

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

public class StreamVs {
    /**
     * 以原始集合操作实现需求
     */
    @Test
    public void oldCartHandle() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        // 打印所有商品
        for (Sku sku : cartSkuList) {
            System.out.println(JSON.toJSONString(sku, true));
        }

        // 图书以外的商品
        List<Sku> notBooksSkuList = new ArrayList<>();
        for (Sku sku : cartSkuList) {
            if (!SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory())) {
                notBooksSkuList.add(sku);
            }
        }

        // 对价格排序
        notBooksSkuList.sort(new Comparator<Sku>() {
            @Override
            public int compare(Sku o1, Sku o2) {
                if (o1.getTotalPrice() > o2.getTotalPrice()) {
                    return -1;
                } else if (o1.getTotalPrice() < o2.getTotalPrice()) {
                    return 1;
                } else {
                    return 0;
                }
            }
        });

        // 其余商品中最贵的两件商品
        List<Sku> top2SkuList = new ArrayList<Sku>();
        for (int i = 0; i < 2; i++) {
            top2SkuList.add(notBooksSkuList.get(i));
        }

        // 求两件商品的总价
        Double money = 0.0;
        for (Sku sku : top2SkuList) {
            money += sku.getTotalPrice();
        }

        // 获取两件商品的名称
        List<String> resultSkuNameList = new ArrayList<>();
        for (Sku sku : top2SkuList) {
            resultSkuNameList.add(sku.getSkuName());
        }

        // 打印结果
        System.out.println(JSON.toJSONString(resultSkuNameList, true));
        System.out.println("商品总价: " + money);
    }
}

这种写法拖泥带水,甚为臃肿

改用流式编程,则令人耳目一新

/**
* 以 Stream 流方式实现需求
*/
@Test
public void newCartHandle() {
    AtomicReference<Double> money = new AtomicReference<>(0.0);
    List<String> resultSkuNameList = CartService.getCartSkuList()
        .stream()
        // 打印商品信息
        .peek(sku -> System.out.println(JSON.toJSONString(sku, true)))
        // 过滤掉所有图书类商品
        .filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
        // 按排序倒序
        .sorted(Comparator.comparing(Sku::getTotalPrice).reversed())
        // 总价 top2 的商品
        .limit(2)
        // 累加金额
        .peek(sku -> money.set(money.get() + sku.getTotalPrice()))
        // 获取商品名称
        .map(Sku::getSkuName)
        // 收集结果
        .collect(Collectors.toList());
    System.out.println(JSON.toJSONString(resultSkuNameList, true));
    System.out.println("商品总价: " + money.get());
}

流式编程介绍

流是 jdk1.8 引入的新成员,以声明式方式处理集合数据

将基础操作链接起来,完成复杂的数据处理流水线,提供透明的并行处理

Java 8新增了Stream、IntStream、LongStream、DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素。上面4个接口中,Stream是一个通用的流接口,而IntStream、LongStream、DoubleStream则代表元素类型为int、long、double的流

独立使用Stream的步骤如下:

  1. 使用Stream或XxxStream的builder()类方法创建该Stream对应的Builder。

  2. 重复调用Builder的add()方法向该流中添加多个元素。

  3. 调用Builder的build()方法获取对应的Stream。

  4. 调用Stream的聚集方法。

此外,Java 8允许使用流式API来操作集合,Collection接口提供了一个stream()默认方法,该方法可返回该集合对应的流,接下来即可通过流式API来操作集合元素

Stream 流方法有如下两个特征:

  • 有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理等。有状态的方法往往需要更大的性能开销

  • 短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素

Stream提供了大量的方法进行聚集操作,可分为:

  • 中间方法(intermediate):中间操作允许流保持打开状态,并允许直接调用后续方法。上面程序中的map()方法就是中间方法。中间方法的返回值是另外一个流

filter(Predicate predicate):过滤Stream中所有不符合predicate的元素。

mapToXxx(ToXxxFunction mapper):使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素

peek(Consumer action):依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试

distinct():该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true)。这是一个有状态的方法

sorted():该方法用于保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法

limit(long maxSize):该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态的、短路方法

  • 末端方法(terminal):末端方法是对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用

forEach(Consumer action):遍历流中所有元素,对每个元素执行action。

toArray():将流中所有元素转换为一个数组。

reduce():该方法有三个重载的版本,都用于通过某种操作来合并流中的元素。

min():返回流中所有元素的最小值。

max():返回流中所有元素的最大值。

count():返回流中所有元素的数量。

anyMatch(Predicate predicate):判断流中是否至少包含一个元素符合Predicate条件。

allMatch(Predicate predicate):判断流中是否每个元素都符合Predicate条件。

noneMatch(Predicate predicate):判断流中是否所有元素都不符合Predicate条件。

findFirst():返回流中的第一个元素。

findAny():返回流中的任意一个元素。

流的常用操作

package stream;

import lambda.cart.CartService;
import lambda.cart.Sku;
import lambda.cart.SkuCategoryEnum;
import org.junit.Before;
import org.junit.Test;

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

public class StreamOperator {

    List<Sku> list;

    @Before
    public void init() {
        list = CartService.getCartSkuList();
    }

    /**
     * filter 过滤掉不符合断言判断的数据
     */
    @Test
    public void filterTest() {
        list.stream()
            .filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
            .forEach(System.out::println);
    }

    /**
     * map 将一个元素转换成另一个元素
     */
    @Test
    public void mapTest() {
        list.stream()
            .map(Sku::getSkuName)
            .forEach(System.out::println);
    }

    /**
     * flatMap 将一个对象转换成流
     */
    @Test
    public void flatMapTest() {
        list.stream()
            .flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
            .forEach(System.out::println);
    }

    /**
     * peek 对流中元素进行遍历操作,与 forEach 类似,但不会销毁流元素
     * 注意结果:peek 与 forEach 交替操作
     * peek 是无状态的
     */
    @Test
    public void peek() {
        list.stream()
            .peek(sku -> System.out.println(sku.getSkuName()))
            .forEach(System.out::println);
    }

    /**
     * 中间添加了有状态的 sorted 后,peek 会先执行后,再执行 sorted, 最后执行 forEach
     * 数据经由 peek 后,先经由 sort 汇总,再交给 forEach 处理
     * 如果没有 sort 层,peek 是无状态的,就会与 forEach 交替执行
     */
    @Test
    public void sortTest() {
        list.stream()
            .peek(sku -> System.out.println(sku.getSkuName()))
            .sorted(Comparator.comparing(Sku::getTotalPrice))
            .forEach(System.out::println);
    }

    /**
     * distinct 去重
     */
    @Test
    public void distinctTest() {
        list.stream()
            .map(Sku::getSkuCategory)
            .distinct()
            .forEach(System.out::println);
    }

    /**
     * skip 过滤掉前 n 条数据
     */
    @Test
    public void skipTest() {
        list.stream()
            .sorted(Comparator.comparing(Sku::getTotalPrice))
            .skip(3)
            .forEach(System.out::println);
    }

    /**
     * limit 只取前 n 条数据
     */
    @Test
    public void limitTest() {
        list.stream()
            .sorted(Comparator.comparing(Sku::getTotalPrice))
            .limit(3)
            .forEach(System.out::println);
    }

    /**
     * 模拟分页
     */
    @Test
    public void limitSkipTest() {
        list.stream()
            .sorted(Comparator.comparing(Sku::getTotalPrice))
            .skip(2 * 3)
            .limit(3)
            .forEach(System.out::println);
    }
}

扩展阅读

关于我
一个文科出身的程序员,追求做个有趣的人,传播有价值的知识,微信公众号主要分享读书思考心得,不会有代码类文章,非程序员的同学请放心订阅
转载须注明出处:https://www.itshutong.com/articles/1023