十四、流式编程(2)

本章概要

  • 中间操作
    • 跟踪和调试
    • 流元素排序
    • 移除元素
    • 应用函数到元素
    • 在 map() 中组合流

中间操作

中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。

跟踪和调试

peek() 操作的目的是帮助调试。它允许你无修改地查看流中的元素。代码示例:

Peeking.java

class Peeking {public static void main(String[] args) throws Exception {FileToWords.stream("Cheese.dat").skip(21).limit(4).map(w -> w + " ").peek(System.out::print).map(String::toUpperCase).peek(System.out::print).map(String::toLowerCase).forEach(System.out::print);}
}

FileToWords.java

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import java.util.stream.Stream;public class FileToWords {public static Stream<String> stream(String filePath)throws Exception {return Files.lines(Paths.get(filePath)).skip(1) // First (comment) line.flatMap(line ->Pattern.compile("\\W+").splitAsStream(line));}
}

Cheese.dat

// streams/Cheese.dat
Not much of a cheese shop really, is it?
Finest in the district, sir.
And what leads you to that conclusion?
Well, it's so clean.
It's certainly uncontaminated by cheese.

输出结果:

在这里插入图片描述

FileToWords 稍后定义,但它的功能实现貌似和之前我们看到的差不多:产生字符串对象的流。之后在其通过管道时调用 peek() 进行处理。

因为 peek() 符合无返回值的 Consumer 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象。

流元素排序

Randoms.java 中,我们熟识了 sorted() 的默认比较器实现。其实它还有另一种形式的实现:传入一个 Comparator 参数。代码示例:

import java.util.*;public class SortedComparator {public static void main(String[] args) throws Exception {FileToWords.stream("D:\\onJava\\myTest\\base\\Cheese.dat").skip(10).limit(10).sorted(Comparator.reverseOrder()).map(w -> w + " ").forEach(System.out::print);}
}

输出结果:

在这里插入图片描述

sorted() 预设了一些默认的比较器。这里我们使用的是反转“自然排序”。当然你也可以把 Lambda 函数作为参数传递给 sorted()

移除元素

  • distinct():在 Randoms.java 类中的 distinct() 可用于消除流中的重复元素。相比创建一个 Set 集合来消除重复,该方法的工作量要少得多。
  • filter(Predicate):过滤操作,保留如下元素:若元素传递给过滤函数产生的结果为true

在下例中,isPrime() 作为过滤函数,用于检测质数。

import java.util.stream.*;import static java.util.stream.LongStream.*;public class Prime {public static Boolean isPrime(long n) {return rangeClosed(2, (long) Math.sqrt(n)).noneMatch(i -> n % i == 0);}public LongStream numbers() {return iterate(2, i -> i + 1).filter(Prime::isPrime);}public static void main(String[] args) {new Prime().numbers().limit(10).forEach(n -> System.out.format("%d ", n));System.out.println();new Prime().numbers().skip(90).limit(10).forEach(n -> System.out.format("%d ", n));}
}

输出结果:

在这里插入图片描述

rangeClosed() 包含了上限值。如果不能整除,即余数不等于 0,则 noneMatch() 操作返回 true,如果出现任何等于 0 的结果则返回 falsenoneMatch() 操作一旦有失败就会退出。

应用函数到元素

  • map(Function):将函数操作应用在输入流的元素中,并将返回值传递到输出流中。
  • mapToInt(ToIntFunction):操作同上,但结果是 IntStream
  • mapToLong(ToLongFunction):操作同上,但结果是 LongStream
  • mapToDouble(ToDoubleFunction):操作同上,但结果是 DoubleStream

在这里,我们使用 map() 映射多种函数到一个字符串流中。代码示例:

import java.util.*;
import java.util.stream.*;
import java.util.function.*;class FunctionMap {static String[] elements = {"12", "", "23", "45"};static Stream<String>testStream() {return Arrays.stream(elements);}static void test(String descr, Function<String, String> func) {System.out.println(" ---( " + descr + " )---");testStream().map(func).forEach(System.out::println);}public static void main(String[] args) {test("add brackets", s -> "[" + s + "]");test("Increment", s -> {try {return Integer.parseInt(s) + 1 + "";} catch (NumberFormatException e) {return s;}});test("Replace", s -> s.replace("2", "9"));test("Take last digit", s -> s.length() > 0 ?s.charAt(s.length() - 1) + "" : s);}
}

输出结果:

在这里插入图片描述

在上面的自增示例中,我们用 Integer.parseInt() 尝试将一个字符串转化为整数。如果字符串不能被转化成为整数就会抛出 NumberFormatException 异常,此时我们就回过头来把原始字符串放到输出流中。

在以上例子中,map() 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。下面代码示例:

// Different input and output types (不同的输入输出类型)import java.util.stream.*;class Numbered {final int n;Numbered(int n) {this.n = n;}@Overridepublic String toString() {return "Numbered(" + n + ")";}
}class FunctionMap2 {public static void main(String[] args) {Stream.of(1, 5, 7, 9, 11, 13).map(Numbered::new).forEach(System.out::println);}
}

输出结果:

在这里插入图片描述

我们将获取到的整数通过构造器 Numbered::new 转化成为 Numbered 类型。

如果使用 Function 返回的结果是数值类型的一种,我们必须使用合适的 mapTo数值类型 进行替代。代码示例:

// Producing numeric output streams( 产生数值输出流)import java.util.stream.*;class FunctionMap3 {public static void main(String[] args) {Stream.of("5", "7", "9").mapToInt(Integer::parseInt).forEach(n -> System.out.format("%d ", n));System.out.println();Stream.of("17", "19", "23").mapToLong(Long::parseLong).forEach(n -> System.out.format("%d ", n));System.out.println();Stream.of("17", "1.9", ".23").mapToDouble(Double::parseDouble).forEach(n -> System.out.format("%f ", n));}
}

输出结果:

在这里插入图片描述

遗憾的是,Java 设计者并没有尽最大努力去消除基本类型。

map() 中组合流

假设我们现在有了一个传入的元素流,并且打算对流元素使用 map() 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要产生一个元素流,而实际却产生了一个元素流的流。

flatMap() 做了两件事:将产生流的函数应用在每个元素上(与 map() 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。

flatMap(Function):当 Function 产生流时使用。

flatMapToInt(Function):当 Function 产生 IntStream 时使用。

flatMapToLong(Function):当 Function 产生 LongStream 时使用。

flatMapToDouble(Function):当 Function 产生 DoubleStream 时使用。

为了弄清它的工作原理,我们从传入一个刻意设计的函数给 map() 开始。该函数接受一个整数并产生一个字符串流:

import java.util.stream.*;public class StreamOfStreams {public static void main(String[] args) {Stream.of(1, 2, 3).map(i -> Stream.of("Gonzo", "Kermit", "Beaker")).map(e -> e.getClass().getName()).forEach(System.out::println);}
}

输出结果:

在这里插入图片描述

我们天真地希望能够得到字符串流,但实际得到的却是“Head”流的流。我们可以使用 flatMap() 解决这个问题:

import java.util.stream.*;public class FlatMap {public static void main(String[] args) {Stream.of(1, 2, 3).flatMap(i -> Stream.of("Gonzo", "Fozzie", "Beaker")).forEach(System.out::println);}
}

输出结果:

在这里插入图片描述

从映射返回的每个流都会自动扁平为组成它的字符串。

下面是另一个演示,我们从一个整数流开始,然后使用每一个整数去创建更多的随机数。

import java.util.*;
import java.util.stream.*;public class StreamOfRandoms {static Random rand = new Random(47);public static void main(String[] args) {Stream.of(1, 2, 3, 4, 5).flatMapToInt(i -> IntStream.concat(rand.ints(0, 100).limit(i), IntStream.of(-1))).forEach(n -> System.out.format("%d ", n));}
}

输出结果:

在这里插入图片描述

在这里我们引入了 concat(),它以参数顺序组合两个流。 如此,我们在每个随机 Integer 流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。

因为 rand.ints() 产生的是一个 IntStream,所以我必须使用 flatMap()concat()of() 的特定整数形式。

让我们再看一下将文件划分为单词流的任务。我们最后使用到的是 FileToWordsRegexp.java,它的问题是需要将整个文件读入行列表中 —— 显然需要存储该列表。而我们真正想要的是创建一个不需要中间存储层的单词流。

下面,我们再使用 flatMap() 来解决这个问题:

import java.nio.file.*;
import java.util.stream.*;
import java.util.regex.Pattern;public class FileToWords {public static Stream<String> stream(String filePath) throws Exception {return Files.lines(Paths.get(filePath)).skip(1) // First (comment) line.flatMap(line ->Pattern.compile("\\W+").splitAsStream(line));}
}

stream() 现在是一个静态方法,因为它可以自己完成整个流创建过程。

注意:\\W+ 是一个正则表达式。表示“非单词字符”,+ 表示“可以出现一次或者多次”。小写形式的 \\w 表示“单词字符”。

我们之前遇到的问题是 Pattern.compile().splitAsStream() 产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 map() 会产生一个单词流的流。幸运的是,flatMap() 可以将元素流的流扁平化为一个简单的元素流。或者,我们可以使用 String.split() 生成一个数组,其可以被 Arrays.stream() 转化成为流:

.flatMap(line -> Arrays.stream(line.split("\\W+"))))

因为有了真正的流(而不是FileToWordsRegexp.java 中基于集合存储的流),所以每次需要一个新的流时,我们都必须从头开始创建,因为流不能被复用:

public class FileToWordsTest {public static void main(String[] args) throws Exception {FileToWords.stream("D:\\onJava\\myTest\\base\\Cheese.dat").limit(7).forEach(s -> System.out.format("%s ", s));System.out.println();FileToWords.stream("D:\\onJava\\myTest\\base\\Cheese.dat").skip(7).limit(2).forEach(s -> System.out.format("%s ", s));}
}

输出结果:

在这里插入图片描述

System.out.format() 中的 %s 表明参数为 String 类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/116287.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Learn Prompt- Midjourney 图片生成:基本设置和预设

/settings指令为模型版本、样式值、质量值和升级器版本等常用选项提供切换按钮。 备注 添加到提示末尾的参数将覆盖/settings中的设置。 模型版本​ 1️⃣ MJ Version 12️⃣ MJ Version 23️⃣ MJ Version 34️⃣ MJ Version 45️⃣ MJ Version 5&#x1f308; Niji Mode&a…

数据结构与算法-时间复杂度与空间复杂度

数据结构与算法 &#x1f388;1.概论&#x1f52d;1.1什么是数据结构&#xff1f;&#x1f52d;1.2什么是算法&#xff1f; &#x1f388;2.算法效率&#x1f52d;2.1如何衡量一个算法的好坏&#xff1f;&#x1f52d;2.2算法的复杂度&#x1f52d;2.3时间复杂度&#x1f4d6;2…

Swift SwiftUI 隐藏键盘

如果仅支持 iOS 15 及更高版本&#xff0c;则可以通过聚焦和取消聚焦来激活和关闭文本字段的键盘。 在最简单的形式中&#xff0c;这是使用 FocusState 属性包装器和 focusable() 修饰符完成的-第一个存储一个布尔值&#xff0c;用于跟踪第二个当前是否被聚焦。 Code struct C…

分类预测 | MATLAB实现WOA-CNN-GRU-Attention数据分类预测

分类预测 | MATLAB实现WOA-CNN-GRU-Attention数据分类预测 目录 分类预测 | MATLAB实现WOA-CNN-GRU-Attention数据分类预测分类效果基本描述模型描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现WOA-CNN-GRU-Attention数据分类预测&#xff0c;运行环境Matlab2021b及以上&…

使用python处理MNIST数据集

文章目录 一. MNIST数据集1.1 什么是MNIST数据集1.2MNIST数据集文件格式1.3使用python访问MNIST数据集文件内容 附录程序源码 一. MNIST数据集 1.1 什么是MNIST数据集 MNIST数据集是入门机器学习/识别模式的最经典数据集之一。最早于1998年Yan Lecun在论文:[Gradient-based l…

yolo增加slide loss,改善样本不平衡问题

slide loss的主要作用是让模型更加关注难例&#xff0c;可以轻微的改善模型在难例检测上的效果 论文地址&#xff1a;https://arxiv.org/pdf/2208.02019.pdf 代码&#xff1a;GitHub - Krasjet-Yu/YOLO-FaceV2: YOLO-FaceV2: A Scale and Occlusion Aware Face Detector 样本不…

2023年“羊城杯”网络安全大赛 决赛 AWDP [Break+Fix] Web方向题解wp 全

终于迎来了我的第一百篇文章。 这次决赛赛制是AWDP。BreakFix&#xff0c;其实就是CTFFix&#xff0c;Fix规则有点难崩。Break和Fix题目是一样的。 总结一下&#xff1a;败北&#xff0c;还是太菜了得继续修炼一下。 一、Break ezSSTI 看到是SSTI&#xff0c;焚靖直接一把梭…

软件设计模式系列之十三——享元模式

1 模式的定义 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它旨在减少内存占用或计算开销&#xff0c;通过共享大量细粒度对象来提高系统的性能。这种模式适用于存在大量相似对象实例&#xff0c;但它们的状态可以外部化&#xff08;e…

2023华为杯数学建模竞赛E题

一、前言 颅内出血&#xff08;ICH&#xff09;是由多种原因引起的颅腔内出血性疾病&#xff0c;既包括自发性出血&#xff0c;又包括创伤导致的继发性出血&#xff0c;诊断与治疗涉及神经外科、神经内科、重症医学科、康复科等多个学科&#xff0c;是临床医师面临的重要挑战。…

免费获取独立ChatGPT账户!!

GPT对于每个科研人员已经成为不可或缺的辅助工具&#xff0c;不同的研究领域和项目具有不同的需求。如在科研编程、绘图领域&#xff1a;1、编程建议和示例代码: 无论你使用的编程语言是Python、R、MATLAB还是其他语言&#xff0c;都可以为你提供相关的代码示例。2、数据可视化…

2023 年 Android 毕业设计选题推荐,200 道 Android 毕业设计题目,避免踩坑

前言 选择一个Android毕业设计题目是一个重要的决策&#xff0c;它将影响你未来几个月的工作。以下是一些关于如何选择一个合适的Android毕业设计题目以及如何避免踩坑的建议&#xff1a; 兴趣和热情&#xff1a;首先&#xff0c;选择你真正感兴趣的领域。如果你对某个领域充…

Python:Django框架的Hello wrold示例

Django是Python的目前很常用的web框架&#xff0c;遵循MVC设计模式。 以下介绍如何安装Django框架&#xff0c;并生成最简单的项目&#xff0c;输出Hello world。(开发工具VScode) 一、安装Django 在VScode终端控制台执行以下指令安装Django python install django 如果要查…