java8流(Stream API)概述

流(Stream)是java8引入的重磅api,使用它可以替换大部分集合操作,重要的是,流相对集合来说更简洁易读、更灵活,性能也更好。

集合操作的痛点

  • 动不动就要遍历数据,代码繁琐、可读性差、维护困难
  • 内存占用大,集合是装载全部数据到内存
  • 需要程序员自己实现并行处理,难度大、易出错
  • 使用集合时性能优化对程序员来说也是一个难点

而上面列举的这些集合痛点,Stream Api能够轻易帮我们解决。

来看一个例子:假设有一个菜单,我们需要遍历出菜单中热量比较底的菜,并对其进行升序排序,然后选出低热量菜的菜名。

传统的做法可能是这样:

    public static void main(String[] args) {
        //菜单
        List<Dish> menu = Arrays.asList(
            new Dish("pork", false, 800, Dish.Type.MEAT),
            new Dish("beef", false, 700, Dish.Type.MEAT),
            new Dish("chicken", false, 400, Dish.Type.MEAT),
            new Dish("french fries", true, 530, Dish.Type.OTHER),
            new Dish("rice", true, 350, Dish.Type.OTHER),
            new Dish("season fruit", true, 120, Dish.Type.OTHER),
            new Dish("pizza", true, 550, Dish.Type.OTHER),
            new Dish("prawns", false, 300, Dish.Type.FISH),
            new Dish("salmon", false, 450, Dish.Type.FISH) );
        //装热量低的菜的集合
        List<Dish> lowCaloricDishes = new ArrayList<>();
        //遍历菜单,筛选出热量低的菜
        for(Dish d: menu){
            if(d.getCalories() < 400){
                lowCaloricDishes.add(d);
            }
        }
       //对低热量菜按热量由低到高排序
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
            public int compare(Dish d1, Dish d2){
                return Integer.compare(d1.getCalories(), d2.getCalories());
            }
        });
        //低热量菜名集合
        List<String> lowCaloricDishesName = new ArrayList<>();
        //遍历出低热量菜名
        for(Dish d: lowCaloricDishes){
            lowCaloricDishesName.add(d.getName());
        }
    }


    static class Dish {
        private final String name;//菜名
        private final boolean vegetarian;//素食标志
        private final int calories;//热量
        private final Type type;//类型
        public Dish(String name, boolean vegetarian, int calories, Type type) {
            this.name = name;
            this.vegetarian = vegetarian;
            this.calories = calories;
            this.type = type;
        }
        public String getName() {
            return name;
        }
        public boolean isVegetarian() {
            return vegetarian;
        }
        public int getCalories() {
            return calories;
        }
        public Type getType() {
            return type;
        }
        @Override
        public String toString() {
            return name;
        }

      public   enum Type { MEAT, FISH, OTHER }
    }



    }

上面的代码需要我们实现每一个细节:遍历了两次、用了一个匿名类,且有一个垃圾变量lowCaloricDishes(它仅被用作一次性能蹭变量),来看java8是如何利用流(Stream)Api将这些繁琐而无聊的细节藏起来的:

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;

List<String> list =   menu.stream().//从集合中获取流
    filter(d-> d.getCalories()<400)//筛选底热量菜
    .sorted(Comparator.comparing(Dish::getCalories))//根据热量排序
    .map(Dish::getName)//获取菜名
    .collect(Collectors.toList());//生成结果:低热量菜名集合


  若要并行处理,只需将menu.stream()改成 menu.parallelStream() ,其它代码无需作任何修改。

对比一下java8与传统的实现,可以发现采用流(Stream)实现有几个显而易见的好处:

  • 代码是以声明性方式写的,就像数据库查询语句那样,你只需要告诉java你想要什么,而不是跟java说,你要这样做(一个for循环),你还要那样判断(if条件语句),这样就隐藏的复杂的实现细节,使代码易于阅读、易于维护,解决了集合的一个痛点;
  • 可以以流水线的方式把各个操作连接起来,代码意图连贯,进一步使其清晰易读,如先筛选(filter)、再排序(sorted)、然后取菜名(map)等;
  • 并行化操作实现简单,只需使用parallelStream()方法即可,解决了集合的又一个痛点;
  • 更易写出可重复使用的代码,灵活性更强,比如可以把过滤条件以Lambda的形式通过参数传入,一个筛选方法就能满足不同的筛选需求;
  • 至于内存与性能优化方面,在这个例子中并不能体现出来,该系列后面的文章中涉及这块,如延时、短路等。

用一句话总结一下就是:java8的流(Stream API)可以让我们以简单的声明性方式写出简洁、易读、灵活性更好、性能更优的代码。

说了这么多流给我们编码还来的好处,那流到底是什么?

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

将定义展开来,解释如下:

  • 元素序列:像集合一样,流也提供了访问特定类型元素序列的接口,但与数据结构集合(主要提供数据存储与访问)不同的是,流的目的在于计算,如前面提到的filter\sorted\map;
  • :流用于计算的数据需要一个数据源,如集合、数组、输入\输出资源;
  • 数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、 map、 reduce、 find、 match、 sort等。流操作可以串行,也可并行执行。

流操作

流操作的两个重要的特征:

  • 流水线:流水线是指很多流操作会返回一个流,这样新的操作就可以连续起来,形成一个流水线操作,如filter之后可以继续map;
  • 内部迭代:批流的迭代操作stream api内部已经帮我们实现了,我们不必关注这个迭代的过程,所以称为内部迭代。

流操作类型:

  • 中间操作
    中间操作会返回另一个流,如filter\map等,让多个操作可以连在一起形成一个查询操作。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理,这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
  • 终端操作
    终端操作会从流中生成结果并将流关闭,如操作collect(toList())。

示例:

 
  1. List<String> names = menu.stream()//获取流
  2. .filter(d -> d.getCalories() > 300)//中间操作
  3. .map(Dish::getName)//中间操作
  4. .limit(3)//中间操作
  5. .collect(toList());//终端操作,关闭流,生成结果List<String>

流使用

概括起来,流的使用包含如下三件事:

  • 一个数据源(如集合)来执行一个查询;
  • 一个中间操作链,形成一条流的流水线;
  • 一个终端操作,执行流水线,并能生成结果。

流的流水线背后的理念类似于构建器模式。在构建器模式中有一个调用链用来设置一套配置(对流来说这就是一个中间操作链),接着是调用built方法(对流来说就是终端操作)。

原文章:http://www.hao124.net/article/94

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值