Java8之stream流

流是什么

流是Java8 API的新功能,它允许使用声明性方式处理集合。可以将其看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理。

例如下面这行代码:

    // 利用多核架构并行处理
    menus.parallelStream()
        // 选出400卡路里以下的菜
        .filter(dish -> dish.getCalories() < 400)
        // 按照卡路里排序
        .sorted(Comparator.comparing(Dish::getCalories))
        // 提取菜名
        .map(Dish::getName)
        // 只选择头三个
        .limit(3L)
        // 将结果保存在List集合里
        .collect(Collectors.toList())
        // 打印结果
        .forEach(System.out::println);

流的优点:

  1. 代码是以声明性方式写的:说明想要完成什么而非说明如何实现该操作。
  2. 可以将多个基础操作链接起来,以此来表达复杂的数据处理流水线,同时保持代码清晰可读。
menus=>start: menus
List=>end: List
filter=>subroutine: filter
sorted=>subroutine: sorted
map=>subroutine: map

menus(right)->filter(right)->sorted(right)->map(right)->List


流的定义

简单来说,流就是从支持数据处理操作的源生成的元素序列。

  • 元素序列:就像集合一样,流提供一个接口,通过该接口可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素。但流的目的在于表达计算
  • 源:流会使用一个提供数据的源,这个源可以是集合、数组或输入资源。若从有序集合生成流时会保留原有的顺序。
  • 数据处理操作:流的数据处理功能支持类似数据库的操作,以及函数式编程语言中的常用操作,如filtermapreducefindmatchsort等。流操作可以顺序执行,也可以并行执行。

流的特点

  1. 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。流水线的操作可以看作对数据源进行数据库式的查询。
  2. 内部迭代——与使用迭代器显示迭代的集合不同,流的迭代操作是在背后进行的。

流的内部优化

我们看一段代码:

menu.stream()
//        筛选出不是蔬菜的食物
        .filter(dish -> {
          System.out.println(dish.toString());
          System.out.println("------------this is filter--------------");
          return !dish.getVegetarian();
        })
//        将对象转换为string类型
        .map(dish->{
          System.out.println(dish.toString());
          System.out.println("------------this is map--------------");
          return dish.toString();
        })
//        只获取四个
        .limit(4L)
        .collect(Collectors.toSet())
        .forEach(System.out::println);

打印在控制台的结果让人惊叹——

Dish(name=pork, vegetarian=false, calories=800, type=MEAT)
------------this is filter--------------
Dish(name=pork, vegetarian=false, calories=800, type=MEAT)
------------this is map--------------
Dish(name=beef, vegetarian=false, calories=700, type=MEAT)
------------this is filter--------------
Dish(name=beef, vegetarian=false, calories=700, type=MEAT)
------------this is map--------------
Dish(name=chick, vegetarian=false, calories=400, type=MEAT)
------------this is filter--------------
Dish(name=chick, vegetarian=false, calories=400, type=MEAT)
------------this is map--------------
Dish(name=french fries, vegetarian=true, calories=530, type=OTHER)
------------this is filter--------------
Dish(name=rice, vegetarian=true, calories=350, type=OTHER)
------------this is filter--------------
Dish(name=season fruit, vegetarian=true, calories=120, type=OTHER)
------------this is filter--------------
Dish(name=pizza, vegetarian=true, calories=550, type=OTHER)
------------this is filter--------------
Dish(name=prawns, vegetarian=false, calories=300, type=FISH)
------------this is filter--------------
Dish(name=prawns, vegetarian=false, calories=300, type=FISH)
------------this is map--------------
Dish(name=chick, vegetarian=false, calories=400, type=MEAT)
Dish(name=prawns, vegetarian=false, calories=300, type=FISH)
Dish(name=beef, vegetarian=false, calories=700, type=MEAT)
Dish(name=pork, vegetarian=false, calories=800, type=MEAT)

这些优化用到了流的延迟性质。尽管filtermap是两个独立的操作,但是它们合并到同一次遍历中。

流的使用

  • filter方法:该方法会接受一个boolean类型结果的函数,并返回一个符合条件的流。

例如:

Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
//        筛选出偶数
        .filter(num -> num % 2 == 0)
        .forEach(System.out::println);

// result: 2, 4, 6, 6, 4, 2
  • distinct方法:该方法会去除流中重复的元素(根据流所生成元素的hashCodeequals方法实现)。

例如:

Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
//        去除重复的元素
        .distinct()
        .forEach(System.out::println);

// result: 1, 2, 3, 4, 5, 6, 7
  • limit方法:该方法会返回一个不超过给定长度的流。

例如:

Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
//        获取前5个对象
        .limit(5L)
        .forEach(System.out::println);

// result: 1, 2, 3, 4, 5
  • skip方法:该方法会返回一个扔掉了前n个元素的流。

例如:

Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
//        扔掉前5个对象
        .skip(5L)
        .forEach(System.out::println);

// result: 6, 7, 6, 5, 4, 3, 2, 1
  • map方法:该方法会对每个元素进行函数运算,并将其映射为一个新的元素(返回函数运算结果)。

例如(对每个元素进行函数运算):

Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
//        对每个元素进行统一的操作
        .map(num -> ++num)
        .forEach(System.out::println);

// result: 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2

例如(对集合内某一元素进行提取:

List<Dish> menu = Arrays.asList(
        new Dish("pork", false, 800, Type.MEAT),
        new Dish("beef", false, 700, Type.MEAT),
        new Dish("chick", false, 400, Type.MEAT),
        new Dish("french fries", true, 530, Type.OTHER),
        new Dish("rice", true, 350, Type.OTHER),
        new Dish("season fruit", true, 120, Type.OTHER)
    );
//    提取每道菜的名字
    List<String> dishNames = menu.stream()
        .map(Dish::getName)
        .collect(Collectors.toList());
    dishNames.forEach(System.out::println);

//result: pork, beef, chick, french fries, rice, season fruit
  • flatMap方法:该方法会将一个流中的每个元素转换为流,一个元素对应一个流,然后把所有的流再连接起来成为一个流。

例如:

List<List<String>> lists = Arrays.asList(
        Arrays.asList("1", "2", "3"),
        Arrays.asList("4", "5", "6"),
        Arrays.asList("7", "8", "9")
    );
    List<String> strings = lists.stream()
//        将每个集合都转换为数组
        .map(List::toArray)
//        将每个数组内的元素转换为流
        .flatMap(Arrays::stream)
        .map(Object::toString)
        .collect(Collectors.toList());
    strings.forEach(System.out::println);

// result: "1", "2", "3", "4", "5", "6", "7", "8", "9"
  • anyMatch方法:该方法会判断流中是否有至少一个元素能匹配给定的判断条件。

例如:

//    判断菜谱里是否有蔬菜
    boolean isVegetarian = menu.stream().anyMatch(Dish::getVegetarian);
  • allMatch方法:该方法会判断流中是否任何元素都能匹配给定的判断条件。

例如:

//    判断菜谱里是否全部都是蔬菜
    boolean isVegetarian = menu.stream().allMatch(Dish::getVegetarian);
  • noneMatch方法:该方法会判断流中是否任何元素都不匹配给定的判断条件。

例如:

//    判断菜谱里是否没有蔬菜
    boolean isVegetarian = menu.stream().noneMatch(Dish::getVegetarian);
  • findAny方法:该方法会返回流中的任意元素。

例如:

Optional<Dish> optionalDish = menu.stream()
        .filter(Dish::getVegetarian)
        .findAny();
//    是否存在蔬菜类食物
    boolean isVegetarian = optionalDish.isPresent();
    if (isVegetarian){
//    如果存在则输出该食物信息
      System.out.println(optionalDish.get().toString());
      return;
    }
    System.out.println("not vegetarian in menu");
  }
  • findFirst方法:该方法会返回流中的地一个元素。

例如:

Optional<Integer> optionalInteger = Stream.of(1, 2, 3, 4, 5, 6)
        .filter(num -> num % 3 == 0)
        .findFirst();
    boolean isHave = optionalInteger.isPresent();
    if (isHave) {
      System.out.println(optionalInteger.get().toString());
      return;
    }
    System.out.println("not num");
  }
  • reduce方法:该方法有接收标识符符合BinaryOperator<T>的表达式,将两个元素结合起来返回一个新的值。

例如:

Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
//    求和
    Integer sum = integerStream.reduce(Integer::sum).get();
//    最大值
    Integer max = integerStream.reduce(Integer::max).get();
//    最小值
    Integer min = integerStream.reduce(Integer::min).get();
    ......
上一篇:Less学习笔记 -- Mixins(混合)一


下一篇:C#网页自动登录和提交POST信息的多种方法