Java8新特性3:Stream1——什么是Stream,Stream的特性,如何使用Stream,Stream与Collection集合的区别

最近打算写一个Stream流的系列,这是基于我看完《java8 in action》英文原版 Stream部分的一些总结,如果你看《java8 in action》有点难以理解 ,那么你可以参考一下我的博客,可以让你清晰Stream的使用和原理。整个流的系列大概按以下方向写:

  • 一、Stream的概念和特性,Stream的使用,Stream与Collection集合的区别。
  • 二、读懂Stream API,多方式创建Stream,学会Stream API的Filter、slic、match、Find、reduce等操作。
  • 三、Stream API的收集器功能,如何使用Stream收集数据
  • 四、了解Stream的并行处理原理

ps:博文中的实例代码,用到 FunctionalInterface 接口的地方都用了lambda或者方法引用,所以,如果你对lambda或者方法引用不熟悉或不了解,先看一下下面两篇博文:

Java8新特性1:lambda表达式入门--由浅入深,从单发步枪迈向自动步枪

Java8新特性2:方法引用--深入理解双冒号::的使用

一、Stream流 入门。

 1、引入流

我们先看看Stream 流的由来:《java8 in action》的作者对现有的在开发中的集合使用,做了一些思考,并引出了流这个东西(上图)。大概意思是:

目前我们在几乎所有开发中都会用到集合,但是目前集合在程序开发中的表现还不够完美,比如你利用集合处理大量数据时,你不得不面对性能问题,不得不考虑进行并行代码的编写,这些工作都是比较繁重的,于是作者便创造了Stream 流。相比较Collection集合来说,Stream在开发中就具有许多独特的优点,这些优点你可以先不用理解,知道就行,我们会在下面的案例代码中直观感受到:

  • 以声明式的方式处理数据集合——更简洁,更易读
  • 可复合——更灵活
  • 可并行——无需写任何多线程代码,Stream API自动处理这些问题,性能更好

2、举个例子,如何用Stream流的编码方式实现需求

我们先不管Stream的概念,先看看作者举的一个例子:有一些菜单menu,现在需要你写一个菜单筛选程序,选出菜单中热量低于400的菜品的菜名。对于这个简单需求,我们分两个方法实现,一个使用jdk8以前的Collection API实现,一个使用jdk8的Stream流实现。我们先简单看看二者的实现代码的区别:

菜品的bean:Dish.java

package com.aigov.java8_newfeatures.stream;

import lombok.Data;

/**
 * @author : aigoV
 * @date :2019/10/21
 * 菜单bean
 **/
@Data
public class Dish {
    private final String name;//菜名
    private final boolean vegetarian;//是否是素食
    private final int calories;//热量,卡路里
    private final Type type;//类型

    public enum Type { MEAT, FISH, OTHER }

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    @Override
    public String toString() {
        return "Dish{" +
                "name='" + name + '\'' +
                '}';
    }
}

需求实现类:simpleStream.java

package com.aigov.java8_newfeatures.stream;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : aigoV
 * @date :2019/10/21
 * Stream流
 **/
public class SimpleStream {

    /**
     * 使用传统的集合方式
     **/
    public static List<String> getDishNamesByCollections(List<Dish> menu) {
        List<Dish> lowCalories = new ArrayList<>();
        //filter 400  过滤
        for (Dish d : menu) {
            if (d.getCalories() < 400) {
                lowCalories.add(d);
            }
        }
        //sort  排序
        Collections.sort(lowCalories, Comparator.comparingInt(Dish::getCalories));
        //nameList for Calories<400  将菜品名放入一个集合
        List<String> dishNameList = new ArrayList<>();
        for (Dish d : lowCalories) {
            dishNameList.add(d.getName());
        }
        return dishNameList;
    }

    /**
     * 用时java8 Stream流的方式处理数据集
     * @param menu
     * @return
     */
    public static List<String> getDishNamesByStream(List<Dish> menu){

        List<String> getDishNamesByStream = menu.parallelStream()//转为可利用多核架构并行执行的Stream
                .filter(d -> d.getCalories()<400)//过滤卡路里小于400的菜
                .sorted(Comparator.comparing(Dish::getCalories))//将过滤后的Stream按卡路里 小->大 排序
                .map(Dish::getName)//提取过滤后的菜品流中的菜名
                .collect(Collectors.toList());//最后将流转为集合
        return getDishNamesByStream;
    }


    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<String> dishNamesByCollections = getDishNamesByCollections(menu);
        System.out.println("集合处理方式:"+dishNamesByCollections);

        List<String> getDishNamesByStream = getDishNamesByStream(menu);
        System.out.println("Stream处理方式:"+getDishNamesByStream);

    }

}

 打印结果:

上面的需求实现类中,我们可以看到Collection和Stream两个实现方式相比较下,Stream在直观上最大的优点就是:代码更加简洁;而且Stream允许你声明式的编码,也就是从代码上直观展示出我们干嘛干嘛(我要筛选热量<400的菜等等),而不再冗杂的循环和if条件;此外,上面代码,我直接用到了parallelStream()方法,这个方法可以利用多核架构并行执行代码,无论是并行处理的性能还是线程条数,都不需要你在考虑。

二、关于Stream API的一些必知概念及特性

1、什么是流?

上面我们只是引出了Stream这个东西,并简单对比了集合与流。那么Stream 流 到底怎么理解呢?

在《java in action》书中的定义是:"a sequence of elements from a source that supports data processing operations.",以我小学一级英语翻译过来就是“支持数据处理操作的一个source(资源?) 中的元素序列”。这个定义似乎有点不太通俗,下面让我们分解一下这个定义,以通俗的的理解什么Stream,Stream的结构和组成。

Sequence of elements(元素序列):这个其实就是 source 里面的东西。对应上面的菜单程序,它其实就Dish部分,即源码中的黄色框框部分:

source(数据源) :Stream流的作用就是操作数据,那么source 就是为Stream提供可操作的源数据的。对应上面的菜单程序,它就是menu(下图。一般,集合、数组或I/OI/O resources 都可以成为Stream的source :

Data processing operations(数据处理操作):上面菜单程序代码中出现的filter、sorted、map、collect,以及我们后来会用到的reduce、find、match等都属于Stream 的一些操作数据的方法接口。这些操作可以顺序进行,也可以并行执行。

Pipelining(管道、流水线):Stream对数据的操作类似数据库查询,也像电子厂的生产流线一样,Stream的每一个中间操作(后面解释什么是中间操作)比如上面的filter、sorted、map,每一步都会返回一个新的流,这些操作全部连起来就是想是一个工厂得生产流水线, like this:

Internal iteration(内部迭代):Stream API 实现了对数据迭代的封装,不用你再像操作集合一样,手动写for循环显示迭代数据。

2、Stream 流的使用。

java8 的 Stream API 提供了很多数据操作接口,这里面一些常用接口其实我们在第一部分那个例子中就看到了,有基础的朋友其实看了第一部分那个例子已经可以进行一些简单的Stream操作。那么现在我们详细来看看Stream的一些基本操作和操作概念,让我们不只停留在会用。

我们想看看我们最开始使用到的菜单程序源码,我做了一些标注:

上面我们引出了两个新的概念,中间操作,终端操作(其实我们上面也提到了),然后你可以看到两类操作:

  • filter、sorted和map可以连成一条流水线。
  • collect 则触发流水线执行,返回集合并关闭它。

3、什么叫中间操作,中间操作的执行顺序?

可以连接起来的Stream流操作,诸如filter或sorted等可以返回一个Stream 流的操作,就叫中间操作。只有存在一个终端操作,且触发了终端操作,中间操作才会开始执行。

现在我们为菜单程序编写一个新的方法,以便直观看到中间操作的执行循序:

    
    //查找菜单中卡路里大于300的菜名 ,只需要输出四个就行
    public static List<String> getDishNamesByStream(List<Dish> menu){

        List<String> getDishNamesByStream =
                menu.stream()
                        .filter(d -> {
                            System.out.println("执行filter—>" + d.getName());
                            return d.getCalories() > 300;
                        })
                        .map(d -> {
                            System.out.println("执行mapping—>" + d.getName());
                            return d.getName();
                        })
                        .limit(4)
                        .collect(toList());

        return getDishNamesByStream;
    }

执行结果:

我们通过控制台的打印信息可以知道,虽然filter和map是两个独立的中间操作,但它们合并到同一次遍历中执行,这就叫作循环合并。

4、什么叫终端操作?

可以启动中间操作和关闭中间流的操作称为终端操作,终端操作会从流的流水线(中间操作)生成结果。其结果是任何非Stream的值,比如可能是List、Integer,甚至void。例:在下面的流水线中,forEach作为一个流的终端操作,它只是打印一数据,并没有返回数据:

menu.stream().forEach(System.out::println);

5、 Stream 流的生命周期

同一个流只能遍历一次,遍历完后,这个流就已经被消费掉了。你如果还需要在遍历,可以从原始数据源那里再获得一个新的流来重新遍历一遍。

举个例子:

同一个流 s 被两次用于forEach的终端操作,此时控制台不报错:

但是你从原始数据源menu那里再获得一个新的流在操作就可以:

6、Stream的内部迭代与Collection的外部迭代

使用Collection需要你自己去做迭代(比如用for-each),这称为外部迭代。 但是Streams API提供了内部迭代,也就是说它帮你把迭代操作实现并封装了,还把得到的流存在了某个地方。举个例子:

集合:用for-each循环外部迭代:

List<String> names = new ArrayList<>();
for(Dish d: menu){
names.add(d.getName());
}

集合:用背后的迭代器做外部迭代

List<String> names = new ArrayList<>();
Iterator<String> iterator = menu.iterator();
while(iterator.hasNext()) {
Dish d = iterator.next();
names.add(d.getName());
}

流:内部迭代

List<String> names = menu.stream()
.map(Dish::getName)
.collect(toList());

是的,你没看到迭代代码

总的来说,Stream API 提供的内部迭代可以自动进行并行处理,或者用更优化的顺序进行处理。相比Java过去集合用的那种外部迭代方法,一旦你写for-each,选择了外部迭代,那你基本上就要自己处理所有的并行问题了,这些优化都是很繁杂的。

 三、Stream 流的入门学习和使用小结

1、流的使用一般包括三件事(参照本文开头的案例代码):

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

2、Stream API提供的一些常用操作接口:

 

java8 新特性解析(更新中):

Java8新特性1:lambda表达式入门--由浅入深,从单发步枪迈向自动步枪 

Java8新特性2:方法引用--深入理解双冒号::的使用

Java8新特性3:Stream1——什么是Stream,Stream的特性,如何使用Stream,Stream与Collection集合的区别

Java8新特性3:Stream2—一文详解Stream API,让你快速理解Stream Api提供的诸多常用方法

Java8新特性3:Stream3—数值流与对象流的转化及其方法使用​​​​​​​

 

 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Java 8中增的Stream是一种处理集合的优雅姿势。 Stream是对集合Collection)对象功能的增强,它能以一种声明的方式来处理数据,实现类似于SQL语句的操作。Stream不会改变原有的数据结构,它会生成一个Stream,同时支持并行化操作。 Stream的核心思想是将数据看作是流,而流上可以进行各种操作,比如过滤、排序、映射等。这样可以将数据处理过程变得非常简洁和灵活。 下面是一些Stream的常用操作: 1. filter:过滤符合条件的元素 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.stream().filter(i -> i % 2 == 0).forEach(System.out::println); //输出2, 4 ``` 2. map:将元素转换成另一种类型 ``` List<String> list = Arrays.asList("apple", "banana", "orange"); list.stream().map(s -> s.toUpperCase()).forEach(System.out::println); //输出APPLE, BANANA, ORANGE ``` 3. sorted:对元素进行排序 ``` List<Integer> list = Arrays.asList(5, 2, 1, 4, 3); list.stream().sorted().forEach(System.out::println); //输出1, 2, 3, 4, 5 ``` 4. distinct:去重 ``` List<Integer> list = Arrays.asList(1, 2, 3, 2, 1); list.stream().distinct().forEach(System.out::println); //输出1, 2, 3 ``` 5. limit:限制元素个数 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.stream().limit(3).forEach(System.out::println); //输出1, 2, 3 ``` 6. skip:跳过元素 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.stream().skip(2).forEach(System.out::println); //输出3, 4, 5 ``` 7. reduce:对元素进行聚合操作 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); int sum = list.stream().reduce(0, (a, b) -> a + b); System.out.println(sum); //输出15 ``` Stream的操作可以组合起来形成一个流水线,每个操作都会返回一个Stream对象,这样就可以形成一个操作序列。最后调用终止操作(如forEach、findAny等)才会触发所有中间操作的执行。 使用Stream处理集合的代码通常比使用传统的循环更简洁,同时也更易于并行化处理,提高了程序的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值