资讯专栏INFORMATION COLUMN

使用流

codercao / 2896人阅读

摘要:将在非空的时候返回值,否则会抛出没有这个元素的异常。构建流现在我们已经能够使用从集合生成流了。由文件生成流不重复的单词数预处理获取流,使用后不用手动关闭流。我们使用得到流,其中每个元素就是文本里的一行。

筛选和切片 filter

filter 会接受一个谓词作为参数,并返回符合该条件的元素流。

        List vegetarianMenu = menu
                .stream()
                .filter(Dish::getVegetarian)
                .collect(Collectors.toList());

会筛选出素食的菜肴。

distinct

distinct 会返回一个元素各异的流,也就是去重。

        List caloriesMenu = menu
                .stream()
                .filter((Dish dish) -> 350 == dish.getCalories())
                .map(Dish::getCalories)
                .distinct()
                .collect(Collectors.toList());

会筛选出卡路里为350的菜肴并去重(米饭和对虾二选一)。

limit(n)

limit(n) 会返回一个不超过给定长度的流。

        List caloriesMenu = menu
                .stream()
                .filter((Dish dish) -> 350 < dish.getCalories())
                .limit(3)
                .collect(Collectors.toList());

List 是有序集合所以会顺序筛选出三个,而如果是 Set 无序的则不会以任何顺序排列。

skip(n)

skip(n) 返回一个人掉了前 n 个元素的流,和 limit(n) 是互补的。

        List caloriesMenu = menu
                .stream()
                .filter((Dish dish) -> 350 < dish.getCalories())
                .skip(3)
                .collect(Collectors.toList());
映射

map 返回一个映射好的元素流。如果我们要找出每个菜肴的名称有多长,那么可以这样思考:第一,我们需要知道菜肴的名称。第二,计算菜肴名称的长度。

        List caloriesMenu = menu
                .stream()
                // 映射名称字符串
                .map(Dish::getName)
                // 映射字符串长度
                .map(String::length)
                .collect(Collectors.toList());
流的扁平化

返回菜肴名称的长度已经做到了,如果有奇怪的需求,比如将菜肴名称打碎成字符并去重……

        List caloriesMenu = menu
                .stream()
                // 映射名称字符串
                .map(Dish::getName)
                // 将字符串分割为字符数组 String[]
                .map((String string) -> string.split(""))
                // Arrays.stream() 可以接收一个数组并产生一个流,再调用 flatMap 将其转成单个流
                .flatMap(Arrays::stream)
                // 去重
                .distinct()
                // 保存
                .collect(Collectors.toList());

扁平化在这里就是让字符数组不是分别映射成一个流,而是映射成流的内容,然后将使用 Arrays::stream 时生成的单个流都被合并起来。

查找和匹配 anyMatch

anyMatch 可以检查谓词是否至少匹配一个元素。

        // 检查是否至少含有一道是素食的菜肴
        Boolean b = menu
                .stream()
                .anyMatch(Dish::getVegetarian);
allMatch

allMatch 可以检查谓词是否全部匹配。

        // 检查是否全部是素食
        Boolean b = menu
                .stream()
                .anyMatch(Dish::getVegetarian);
noneMatch

noneMatch 可以检查谓词是否全部不匹配。

        // 检查是否全部不是素食
        Boolean b = menu
                .stream()
                .noneMatch(Dish::getVegetarian);

以上这三种操作都用到了所谓的短路,也和 Java 中!、&&和||运算符意义一样。

findAny

findAny 将返回当前流中的任意元素。

        Optional dishOptional = menu
                .stream()
                .findAny();

Optional 是一个容器类,代表一个值存在或不存在。有可能 findAny 什么也没找到,所以如果用 Dish 对象直接装可能会报空指针错误。本章不会深入了解 Optional,但可以先简单介绍一下这个类的一些有用的方法。

isPresent() 将在非空的时候返回 true。

ifPresent(Consumer consumer) 将在非空的时候执行 Consumer 的代码,再第二章的时候我们知道 Consumer 可以接受一个对象并进行操作。

T get() 将在非空的时候返回值,否则会抛出没有这个元素的异常。

T orElse(T other) 将在非空的时候返回值,否则返回一个默认值。

比如上面的代码可以变成这样

        menu.stream().findAny().ifPresent(System.out::println);

就可以打印输出任意菜肴的信息。

findFirst

findAny 类似,只不过返回有序集合的第一个元素。

        Optional dishOptional = menu
                .stream()
                .findFirst();

那么 findAnyfindFirst 有什么区别呢?在并行计算里面,找到第一个元素会需要很多思考,所以一般在并行处理时用 findAny

归纳

reduce 操作可以吧一个流中的元素组合起来,比如菜单里的总卡路里。此类查询需要将流中所有元素反复结合起来,得到一个值。这样的查询操作可以被归类为 归约 操作,用函数式编程语言的术语来说,这称为 折叠

元素求和

平常使用 for-each 来处理总卡路里我们一般会这样做

        Integer integer = 0;
        for (Dish dish : menu) {
            integer += dish.getCalories();
        }

在流中可以使用 reduce 方法来处理

        Integer integer = menu
                .stream()
                .map(Dish::getCalories)
                .reduce(0, (Integer a, Integer b) -> a + b);

好像有点复杂,没关系,接下来可以详细讲解。

首先我们用 map 将 Dish 对象映射成其卡路里值的 Integer 类型。然后调用 reduce 将每个映射好的 Integer 组合(相加)。其中 reduce 方法第一个参数代表初始值,后面跟着一个 BinaryOperator 的函数式接口。这个函数式接口目的就是将两个元素组合。

可以看一下 BinaryOperator 的源码

@FunctionalInterface
public interface BinaryOperator extends BiFunction {
    public static  BinaryOperator minBy(Comparator comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    public static  BinaryOperator maxBy(Comparator comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

实际上我们调用的是第三章有提到过的 BiFunction 里的 apply 方法

@FunctionalInterface
public interface BiFunction {
    R apply(T t, U u);
}

BinaryOperator 它接受两个同类型参数并返回一个同类型的对象。

所以我们可以这样来写

        BinaryOperator integerBinaryOperator = (Integer a, Integer b) -> a + b;

代表计算 a + b。而 reduce 自带循环,所以会在背后进行类似于 a += b 一样的逻辑操作。reduce还有一个重载的版本,可以不接受初始值返回一个 Optional 对象。

        Optional integer = menu
                .stream()
                .map(Dish::getCalories)
                .reduce((Integer a, Integer b) -> a + b);

用 Optional 的意义与之前一样,考虑到了集合为空的情况。

最大和最小

reduce 同样可以用来做大小比较,因为其需要的 BinaryOperator 自带有比较方法。

最大

        Optional integer = menu
                .stream()
                .map(Dish::getCalories)
                .reduce(Integer::max);

其中 reduce(Integer::max) 是

        reduce((Integer integer1, Integer integer2) -> Integer.max(integer1, integer2))

的缩写。计算最小值只需要将 max 换成 min 就行。归纳方法的对比传统的 for-each 优势是可以把内部迭代抽象化,这让其内部可以并行化而不用我们自己去实现并行处理。

但是这种并行是有限制的,不是将所有的 stream 换成 parallelStream 就行了。

像 map 或 filter 等操作会从输入流中获取每一个元素并在输出流中得到至多一个结果。这些操作一般是无状态的,做并行是可行的。

像 reduce、max 等操作需要内部状态来累积结果,因为求和肯定是需要之前数的和加上现在选择的这个元素,但无论哪个都是有限的,所以称为有界,是可以直接做并行。

相反,如同 distinct 操作,是需要接受一个流再生成一个流,并且去重操作是需要知道先前的历史流是什么样的,例如把所有质数倒序,就需要最大的那个质数,但这是不存在的,所以称为无界状态。做并行需要好生思考一番。

关于去重,其实可以换一种思路,将有序集合(List)转为无序集合(Set)就自动去重了。

        Set caloriesMenu = menu
                .stream()
                .filter((Dish dish) -> 350 == dish.getCalories())
                .map(Dish::getCalories)
                .collect(Collectors.toSet());
数值范围

在和数字打交道时,比较常用的就是在一个数值范围内生成数字。Java 8引入了两个可以用于 IntStream 和 LongStream 的静态方法,帮助生成范围:range 和 rangeClosed。是不包含结束值的,比如传入(0,100)就会运算到99结束。

比如我们需要统计0~100里偶数的个数(包含100)

        long l = IntStream
                .rangeClosed(0, 100)
                .filter((int n) -> n % 2 == 0)
                .count();

这样就统计出了51个偶数个数,如果不包含100就用 range,会统计50个。

构建流

现在我们已经能够使用 Stream 从集合生成流了。那么如何从值序列、数组和文件生成流,甚至函数来创建无限流?

创建一个空流
        Stream stringStream = Stream.empty();
由值创建流
        Stream stringStream = Stream.of("qwe", "asd", "zxc");
        stringStream.map(String::toUpperCase).forEach(System.out::println);

这样会把小写字符串全部转成大写。

由数组创建流
        int[] ints = {1, 2, 3, 4, 5};
        int sum = Arrays.stream(ints).sum();

这样会把所有的数字相加。

由文件生成流
        /*
        /Users/cciradih/java:
        
        Java Platform, Standard Edition (Java SE) lets you develop and deploy Java applications on desktops and servers,
        as well as in today"s demanding embedded environments. Java offers the rich user interface, performance,
        versatility, portability, and security that today"s applications require.
         */


        // 不重复的单词数
        long uniqueWords = 0;
        // 预处理获取流,使用后不用手动关闭流。
        try (Stream stringStream = Files.lines(Paths.get("/Users/cciradih/java"), Charset.defaultCharset())) {
            // 从流中统计
            uniqueWords = stringStream
                    // 扁平化字符串数组
                    .flatMap((String line) -> Arrays.stream(line.split(" ")))
                    // 去重
                    .distinct()
                    // 统计
                    .count();
        } catch (IOException e) {
            e.printStackTrace();
        }

这里是用到了新的 NIO,以便利用 Stream。我们使用 Files.lines 得到流,其中每个元素就是文本里的一行。然后使用 split 拆分成单词,并用 flatMap 将其扁平化。最后用 distinct 去重再用 count 统计。

由函数生成流

Stream 提供了两个静态方法来从函数生成流 iterate 和 gengrate。这两个操作都可以创建无线流。

iterate

        Stream.iterate(0, (Integer integer) -> integer + 2);

这会创建一个从0开始无限的偶数流。

gengrate

        Stream.generate(Math::random);

这会创建一个0到1之间无限的随机数。

这一章详细讲了流的使用场景和需要注意的地方,特别是和传统的操作做对比,能更好地支持并行处理。

Java 8 实战 第五章 使用流 读书笔记

欢迎加入咖啡馆的春天(338147322)。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70209.html

相关文章

  • 第十五章 输入输出系统

    摘要:在包下主要包括输入输出两种流,每种输入输出流又可分为字节流和字符流两大类。输入输出是从程序运行所在的内存的角度而言的。的输入流主要由和作为基类,而输出流主要由和作为基类。 本章主要参考和摘自疯狂java讲义上面的(java编程思想的后面看过后有新的内容再补充进去吧)。  输入输出是所有程序都必需的部分————使用输入机制允许程序读取外部数据(包括磁盘、光盘等存储设备上的数据和用户输入的...

    hankkin 评论0 收藏0
  • 高薪程序员&amp;面试题精讲系列22之说说Java的IO,常用哪些IO

    摘要:一面试题及剖析今日面试题今天壹哥带各位复习一块可能会令初学者比较头疼的内容,起码当时让我很有些头疼的内容,那就是流。在这里壹哥会从两部分展开介绍流,即与流。除此之外尽量使用字节流。关闭此输入流并释放与流相关联的任何系统资源。 一. 面试题及剖析 1. 今日面试题 今天 壹哥 带各位复习一块可...

    fnngj 评论0 收藏0
  • Java IO

    摘要:分类一按操作方式类结构字节流和字符流字节流以字节为单位,每次次读入或读出是位数据。该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区字符数组,通过内部的指针来操作字符数组中的数据。 分类一:按操作方式(类结构) 字节流和字符流: 字节流:以字节为单位,每次次读入或读出是8位数据。可以读任何类型数据。 字符流:以字符为单位,每次次读入或读出是16位数据。其只能读取字符类...

    Salamander 评论0 收藏0
  • 第十一章-IO#yyds干货盘点#

    摘要:是一个系统支持的所有字符的集合,包括各国家文字标点符号图形符号数字等字符集简体中文码表。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等字符集为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码标准万国码。 1 File1.1 File类的概述和构造方法File: 它是文件和目录路径名的抽象...

    不知名网友 评论0 收藏0
  • Java 输入/输出 I/O RandomAccessFile

    摘要:当使用节点流进行输入输出时,程序直接连接到实际的数据源,和时间的输入输出节点连接处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读写功能,处理流也被称为高级流。 文件的编码 文本文件就是字节序列,可以是任意编码形式。在中文操作系统上直接创建文本文件,则该文本文件只能识别ANSI编码,其他编码方式会产生乱码 package imooc.io; import java...

    Eirunye 评论0 收藏0
  • Java IO (一),理解

    摘要:的是实现输入输出的基础中把不同的输入输出源键盘文件网络连接抽象的表述为流流的分类输入流和输出流按照流的流向来分输入流只能从中读数据而不能向其中写数据输出流只能向其中写出数据而不能从中读取数据此处的输入输出涉及到一个方向问题数据从内存到硬盘被 Java的IO是实现输入输出的基础,Java中把不同的输入/输出源(键盘,文件,网络连接)抽象的表述为流,stream. 流的分类 输入流和输...

    罗志环 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<