zoukankan      html  css  js  c++  java
  • Java Stream流式思想

    说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

    引言

    传统集合的多步遍历代码

    几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:

    import java.util.List;
    import java.util.ArrayList;
    import java.util.Collections;
    
    public class DemoForEach {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
    
            Collections.addAll(list, "Java", "C", "Python", "Hadoop", "Spark");
    
            for (String s : list) {
                System.out.println(s);
            }
        }
    }

    运行程序,控制台输出:

    Java
    C
    Python
    Hadoop
    Spark

    这是一段非常简单的集合遍历操作:对集合中的每一个字符串都进行打印输出操作。

     

    循环遍历的弊端

    Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How)。

    现在,我们仔细体会一下上例代码,可以发现:

    • for循环的语法就是“怎么做”

    • for循环的循环体才是“做什么”

    为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

     

    试想一下,如果希望对集合中的元素进行筛选过滤:

    1.  将集合A根据条件一过滤为子集B;

    2. 然后再根据条件二过滤为子集C。

    那怎么办?在Java 8之前的做法可能为:

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class DemoNormalFilter {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            Collections.addAll(list, "Java", "C", "Python", "Hadoop", "Spark");
    
            System.out.print("筛选前的集合:");
            for (String s : list) {
                System.out.print(s + ",");
            }
            System.out.println();
    
            System.out.print("经过条件1筛选后的集合:");
            for (String s : list) {
                if (s.length() >= 4) {
                    System.out.print(s + ",");
                }
            }
            System.out.println();
    
            System.out.print("经过条件2筛选后的集合:");
            for (String s : list) {
                if (s.length() >= 5) {
                    System.out.print(s + ",");
                }
            }
            System.out.println();
        }
    }

    运行程序,控制台输出:

    筛选前的集合:Java,C,Python,Hadoop,Spark,
    经过条件1筛选后的集合:Java,Python,Hadoop,Spark,
    经过条件2筛选后的集合:Python,Hadoop,Spark,

    这段代码中含有三个循环,每一个作用不同:

    1、首先从头到尾,遍历输出集合。

    2、然后筛选字符串长度大于等于4的元素,并输出。

    3、最后筛选字符串长度大于等于5的元素,并输出。

     

    每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。

    那么,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?下面我们来看一下Stream的更优写法。

     

    Stream的更优写法

    下面来看一下借助Java 8的Stream API,什么才叫优雅:

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class Demo01Stream {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            Collections.addAll(list, "Java", "C", "Python", "Hadoop", "Spark");
    
            list.stream()
                    .filter((s) -> s.length() >= 4)
                    .filter((s) -> s.length() >= 5)
                    .forEach((s) -> System.out.println(s));
        }
    }

    运行程序,控制台输出:

    Python
    Hadoop
    Spark

    筛选的结果与上面的例子一致。

    直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤长度小于4的、过滤长度小于5的、逐一打印。代码 中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。

     

    上面程序用到的方法:

    stream()方法

    利用stream()方法,来获取流。该方法是java.util.Collection接口中的一个默认方法,方法源码如下:

    // 返回以该集合为源的序列流。
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    filter()方法

    该方法是java.util.stream包中的Stream接口里的一个抽象方法,方法源码如下:

    // 返回由与给定 {predicate} 匹配的此流元素组成的流。
    Stream<T> filter(Predicate<? super T> predicate);

    该方法的返回值是一个流,传入的参数是一个函数式接口:java.util.function.Predicate,该接口可以对某种类型的数据进行判断,然后返回一个布尔值。

    forEach()方法

    该方法是java.util.stream包中Stream接口里的一个抽象方法,方法源码如下:

    // 对该流的每个元素执行操作。
    void forEach(Consumer<? super T> action);

    该方法没有返回值,传入的参数是一个函数式接口:java.util.function.Consumer。它的作用是:消费一个数据, 其数据类型由泛型决定。

    上面例子的代码可以进行改善:

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class Demo02Stream {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            Collections.addAll(list, "Java", "C", "Python", "Hadoop", "Spark");
    
            list.stream()
                    .filter((s) -> s.length() >= 4)
                    .filter((s) -> s.length() >= 5)
                    .forEach(System.out::println);
        }
    }

    其实就是修改了forEach()方法中传入的参数,

    System.out::println
    (s) -> System.out.println(s)

    这两者是等价的。

    流式思想概述

    注意:请暂时忘记对传统IO流的固有印象!

    整体来看,流式思想类似于工厂车间的“生产流水线”。

     

    当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤 方案,然后再按照方案去执行它。

    这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字 3是最终结果。

     

    这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性

    备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据构,其本身并不存储任何元素(或其地址值)。

    Stream(流)是一个来自数据源的元素队列

    • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。

    • 数据源:流的来源。 可以是集合,数组等。

    和以前的Collection操作不同, Stream操作还有两个基础的特征:

    • Pipelining:中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。

    • 内部迭代:以前对集合遍历都是通过Iterator或者增强for的方式,显式的在集合外部进行迭代,这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

     

    当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。

     

  • 相关阅读:
    应用默认编码不对的问题定位
    以http server为例简要分析netty3实现
    用qemu+gdb tcp server+CDT调试linux内核启动-起步
    用virtualbox+模拟串口+CDT调试linux内核 TCP/IP协议栈-起步
    【转】常见容错机制
    python文档注释参数获取
    scrapy爬取图片
    xpath语法
    python爬虫爬取赶集网数据
    爬虫小总结
  • 原文地址:https://www.cnblogs.com/liyihua/p/12288600.html
Copyright © 2011-2022 走看看