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 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。

原文地址:https://www.cnblogs.com/liyihua/p/12288600.html

时间: 2024-10-01 01:17:50

Java Stream流式思想的相关文章

Java学习:Stream流式思想

Stream流 Java 8 API添加了一种新的机制——Stream(流).Stream和IO流不是一回事. 流式思想:像生产流水线一样,一个操作接一个操作. 使用Stream流的步骤:数据源→转换成流→操作1→操作2→…… 数据源(source):可以是集合.数组等. Stream操作有两个基础特征: Pipelining(流水线):流操作会返回流对象(新的对象),以便后续继续进行流操作. 内部迭代:不需要像for循环或Iterator一样进行显式的迭代. 遍历及过滤集合中的元素 使用传统方

jdk1.8新特性Stream流式处理

前言: 之前在学习JDK1.8的时候接触到了1.8的两个重要的新特新,一个是lambda函数式编程.另一个则是接口的变化,在这里还有一个非常重要的内容就是strema流式处理方式,在传统的java代码中,如果我们要对一个集合做一些操作,就必须遍历这个集合那个集合中的元素逐个操作,而在JDK1.8引入stream流式处理方式后,我们可以不用对集合进行遍历可以直接操作集合得到我们想要的结果,在这里看一个小例子: 例如在这里有一个存放字符串的list集合,我需要获取list集合字符长度是三且不为空的字

一文带你入门Java Stream流,太强了

两个星期以前,就有读者强烈要求我写一篇 Java Stream 流的文章,我说市面上不是已经有很多了吗,结果你猜他怎么说:“就想看你写的啊!”你看你看,多么苍白的喜欢啊.那就“勉为其难”写一篇吧,嘻嘻. 单从“Stream”这个单词上来看,它似乎和 java.io 包下的 InputStream 和 OutputStream 有些关系.实际上呢,没毛关系.Java 8 新增的 Stream 是为了解放程序员操作集合(Collection)时的生产力,之所以能解放,很大一部分原因可以归功于同时出现

JavaScript AJAX stream 流式显示

当使用AJAX进行信息交互的时候,如果服务器返回的信息比较大,那么相对于传送完成之后的统一显示,流式显示就比较友好了. 流式实现 原理就是设置定时器,定时的查看AJAX对象的状态并更新内容,如果传送完成,就取消定时器. function ajax_stream(url,data,element) { var xmlHttp=null; if (window.XMLHttpRequest) {// code for IE7, Firefox, Opera, etc. xmlHttp=new XML

Java Stream 流的使用

目录 1. 筛选和切片 2. 映射 3. 查找和匹配 4. 归约 小结 1. 筛选和切片 用谓词筛选 filter方法接受一个返回boolean的方法. List<Dish> vegetarianMenu=menu.stream().filter(Dish::isVegetarian) .collect(toList()); distinct去重 distinct方法,根据流中元素的hashCode和equals方法.例: List<Integer> numbers = Array

Java8 流式 API(`java.util.stream`)

熟悉 ES6 的开发者,肯定对数组的一些方法不是很陌生:map.filter 等.在对一组对象进行统一操作时,利用这些方法写出来的代码比常规的迭代代码更加的简练.在 C? 中,有 LINQ 来实现.那么在 Java 中有这样的操作吗?答案是有的,Java8 中引入了大量新特性,其中一个就是 Java 的流式 API. 在 Java 8 中,流(Stream)与迭代器类似,都是用来对集合内的元素进行某些操作.它们之间最大的差别,是对迭代器的每个操作都会即时生效,而对流的操作则不是这样.流的操作有两

java中的Stream流

java中的Stream流 说到Stream便容易想到I/O Stream,而实际上,谁规定"流"就一定是"IO流"呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端. 一.引言 传统集合的多步遍历代码 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作.而当我们需要对集合中的元 素进行操作的时候,除了必需的添加.删除.获取外,典型的就是集合遍历.

Java 之 Stream 流

Stream流 在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端 一.传统遍历 1.传统集合的多步遍历代码 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作.而当我们需要对集合中的元素进行操作的时候,除了必需的添加.删除.获取外,最典型的就是集合遍历.例如: 1 import java.util.ArrayList; 2 import java.util.List; 3 4 pub

Java Swing 之流式布局管理器

/** * java 之流式布局 * @author gao */ package com.gao; import java.awt.FlowLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; @SuppressWarnings("serial") public class Flow