Java8实战-总结9
Java8实战-总结9
- Lambda表达式
- 把Lambda付诸实践:环绕执行模式
- 第1步:记得行为参数化
- 第2步:使用函数式接口来传递行为
- 第3步:执行一个行为
- 第4步:传递Lambda
- 使用函数式接口
- Predicate
- Consumer
- Function
- 原始类型特化
Lambda表达式
把Lambda付诸实践:环绕执行模式
通过一个例子,看看在实践中如何利用Lambda和行为参数化来让代码更为灵活,更为简洁。资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式,如下图所示。例如,在以下代码中,中间部分就是从一个文件中读取一行所需的模板代码(注意使用了Java 7中的带资源的try语句,它已经简化了代码,因为不需要显式地关闭资源了):
public static String processFile() throws IOException {//这就是做有用工作的那行代码try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {return br.readLine();}
}

第1步:记得行为参数化
现在这段代码是有局限的。只能读文件的第一行。如果想要返回头两行,甚至是返回使用最频繁的词,该怎么办呢?在理想的情况下,要重用执行设置和清理的代码,并告诉processFile方法对文件执行不同的操作。这听起来是不是很耳熟?是的,需要把processFile的行为参数化。需要一种方法把行为传递给processFile,以便它可以利用BufferedReader执行不同的行为。
传递行为正是Lambda的拿手好戏。那要是想一次读两行,这个新的processFile方法看起来又该是什么样的呢?基本上,需要一个接收BufferedReader并返回String的Lambda。例如,下面就是从BufferedReader中打印两行的写法:
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
第2步:使用函数式接口来传递行为
前面解释过了,Lambda仅可用于上下文是函数式接口的情况。需要创建一个能匹配BufferedReader -> String,还可以抛出IOException异常的接口。把这一接口叫作BufferedReaderProcessor吧。
@FunctionalInterfacepublic interface BufferedReaderProcessor {String process(BufferedReader b) throws IOException;}
现在就可以把这个接口作为新的processFile方法的参数了:
public static String processFile(BufferedReaderProcessor p) throws IOException {}
第3步:执行一个行为
任何BufferedReader -> String形式的Lambda都可以作为参数来传递,因为它们符合BufferedReaderProcessor接口中定义的process方法的签名。现在只需要一种方法在processFile主体内执行Lambda所代表的代码。请记住,Lambda表达式允许直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。因此,可以在processFile主体内,对得到的BufferedReaderProcessor对象调用process方法执行处理:
public static String processFile(BufferedReaderProcessor p) throws
IOException {try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {//处理BufferedReaderreturn p.process(br);}
}
第4步:传递Lambda
现在你就可以通过传递不同的Lambda重用processFile方法,并以不同的方式处理文件了。处理一行:
String oneLine = processFile((BufferedReader br)-> br.readLine());
处理两行:
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
下图总结了所采取的使pocessFile方法更灵活的四个步骤:

使用函数式接口
函数式接口定义且只定义了一个抽象方法。函数式接口很有用,因为抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。所以为了应用不同的Lambda表达式,需要一套能够描述常见函数描述符的函数式接口。Java API中已经有了几个函数式接口,比如Comparable、Runnable和Callable。
Java 8的库设计师在java.util.function包中引入了几个新的函数式接口:Predicate、Consumer和Function。
Predicate
java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。这恰恰和先前创建的一样,现在就可以直接使用了。在需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,可以定义一个接受String对象的Lambda表达式,如下所示:
@FunctionalInterfacepublic interface Predicate<T> {boolean test(T t);}public static <T> List<T> filter(List<T> list, Predicate<T> p) {List<T> results = new ArrayList<>();for(T s : list) {if(p.test(s)) {results.add(s);}}return results;}Predicatec<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();List<String> nonEmpty = filter(listofStrings, nonEmptyStringPredicate);
如果去查Predicate接口的Javadoc说明,可能会注意到诸如and和or等其他方法。现在不用太计较这些。
Consumer
java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。比如,可以用它来创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作。在下面的代码中,就可以使用这个forEach方法,并配合Lambda来打印列表中的所有元素:
@FunctionalInterface
public interface Consumer<T> {void accept(T t);
}public static <T> void forEach(List<T> list, Consumer<T> c) {for(T i : list){c.accept(i);}
}//Lambda是Consumer中accept方法的实现
forEach(Arrays.asList(1,2,3,4,5),(Integer i) -> System.out.println(i));
Function
java.util.function.Function<T,R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。在下面的代码中,将展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个String长度的Integer列表。
@FunctionalInterface
public interface Punction<T, R> {R apply(T t);
}public static <T,R> List<R> map(List<T> list, Function<T,R> f) {List<R> result = new ArrayList<>();for(T s : list) {result.add(f.apply(s));}return result;
}//[7,2,6]
//Lambda是Punction接口的apply方法的实现
List<Integer> 1 = map(Arrays.asList("lambdas","in","action"),(String s)-> s.length());
原始类型特化
三个泛型函数式接口:Predicate<T>、Consumer<T>和Function<T, R>。还有些函数式接口专为某些类型而设计。
Java类型要么是引用类型(比如Byte、Integer、Object、List),要么是原始类型(比如int、double、byte、char)。但是泛型(比如Consumer<T>中的T)只能绑定到引用类型。这是由泛型内部的实现方式造成的。因此,在Java里有一个将原始类型转换为对应的引用类型的机制。这个机制叫作装箱(boxing)。相反的操作,也就是将引用类型转换为对应的原始类型,叫作拆箱(unboxing)。Java还有一个自动装箱机制来帮助程序员执行这一任务:装箱和拆箱操作是自动完成的。比如,这就是为什么下面的代码是有效的(一个int被装箱成为Integer):
List<Integer> list = new ArrayList<>();for(int i = 300; i < 400; i++) {list.add(i);}
但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
Java 8为前面所说的函数式接口带来了一个专门的版本,以便在输入和输出都是原始类型时避免自动装箱的操作。比如,在下面的代码中,使用IntPredicate就避免了对值1000进行装箱操作,但要是用Predicate<Integer>就会把参数1000装箱到一个Integer对象中:
public interface IntPredicate {boolean test(int t);
}//true(无装箱)
IntPredicate evenNumbers = (int i) -> i % 2 == 0;
evenNumbers.test(1000);//false(装箱)
Predicate<Integer> oddNumbers =(Integer i) -> i % 2 == 1;
oddNumbers.test(1000);
一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如DoublePredicate、IntConsumer、LongBinaryoperator、IntFunction等。Function接口还有针对输出参数类型的变种:ToIntFunction<T>、IntToDoubleFunction等。
下表总结了Java API中提供的最常用的函数式接口及其函数描述符。请记得这只是一个起点。如果有需要,可以自己设计一个。请记住,(T, U) -> R的表达方式展示了应当如何思考一个函数描述符。表的左侧代表了参数类型。这里它代表一个函数,具有两个参数,分别为泛型T和U,返回类型为R。


测验:函数式接口
对于下列函数描述符(即Lambda表达式的签名),请构造一个可以利用这些函数式接口的有效Lambda表达式:(1)T -> R
(2)(int, int) -> int
(3)T -> void
(4)() -> T
(5)(T, U) -> R答案如下。
(1)Function<T,R>不错。它一般用于将类型T的对象转换为类型R的对象(比如Function<Apple, Integer>用来提取苹果的重量)。
(2)IntBinaryOperator具有唯一一个抽象方法,叫作applyAsInt,它代表的函数描述符是(int, int)-> int。
(3) Consumer<T>具有唯一一个抽象方法叫作accept,代表的函数描述符是T -> void。
(4)Supplier<T>具有唯一一个抽象方法叫作get,代表的函数描述符是() -> T。或者,Callable<T>具有唯一一个抽象方法叫作call,代表的函数描述符是() -> T。
(5)BiFunction<T, U, R>具有唯一一个抽象方法叫作apply,代表的函数描述符是(T, U) -> R。
下表总结了一些使用案例、Lambda的例子,以及可以使用的函数式接口:

异常、Lambda,还有函数式接口又是怎么回事呢?请注意,任何函数式接口都不允许抛出受检异常(checked exception)。如果需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,
并声明受检异常,或者把Lambda包在一个try/catch块中。比如,函数式接口BufferedReaderProcessor,它显式声明了一个IOException:
@FunctionalInterface
public interface BufferedReaderProcessor {String process(BufferedReader b) throws IOException;
}
BufferedReaderProcessor p =(BufferedReader br)-> br.readLine();但可能是在使用一个接受函数式接口的API,比如Function<T, R>,没有办法自己创建一个。这种情况下,可以显式捕捉受检异常:
Function<BufferedReader, String> f = (BufferedReader b) -> {try {return b.readLine();}catch(IOException e) {throw new RuntimeException(e);}
};
相关文章:
Java8实战-总结9
Java8实战-总结9 Lambda表达式把Lambda付诸实践:环绕执行模式第1步:记得行为参数化第2步:使用函数式接口来传递行为第3步:执行一个行为第4步:传递Lambda 使用函数式接口PredicateConsumerFunction原始类型特化 Lambda表…...
大数据开发面试必问:Hive调优技巧系列一
Hive必问调优 Hive 调优拆解:Hive SQL 几乎是每一位互联网分析师的必备技能,相信很多小伙伴都有被面试官问到 Hive 优化问题的经历。所以掌握扎实的 HQL 基础尤为重要,hive优化也是小伙伴应该掌握的一项技能,本篇文章具体从hive建表优化、HQ…...
Jupyter Notebook 7重磅发布,新增多个特性!
本文分享Jupyter Notebook大版本v7.0.0更新亮点,及简单测试! 近日,Jupyter Notebook大版本v7.0.0更新,Jupyter Notebook 7基于JupyterLab,因此它包含了过去几年JupyterLab中添加的许多新功能和改进,部分亮…...
linux V4L2子系统——v4l2架构(1)之整体架构
概述 V4L(Video for Linux)是Linux内核中关于视频设备的API接口,涉及视频设备的音频和视频信息采集及处理、视频设备的控制。V4L出现于Linux内核2.1版本,经过修改bug和添加功能,Linux内核2.5版本推出了V4L2(…...
Qt信号与槽机制的本质
引入 对象与对象之间的通信有多个方式,如果我们要提供一种对象之间的通信机制。这种机制,要能够给两个不同对象中的函数建立映射关系,前者被调用时后者也能被自动调用。 再深入一些,两个对象如果都互相不知道对方的存在ÿ…...
Linux:入门学习知识及常见指令
文章目录 入门介绍操作系统的概念Linux机器的使用Linux上的指令 对文件知识的补充文件的定义和一些含义文件和目录的存储绝对路径和相对路径 ls指令pwd指令cd指令touch指令mkdir指令rmdir指令rm指令man指令cp指令mv指令cat指令more指令echo指令输出重定向 less指令find指令grep…...
K8s:Kubernetes 故障排除方法论
写在前面 博文内容为节译整理文中提到的工具大部分是商业软件,不是开源的,作为了解理解不足小伙伴帮忙指正 对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它…...
TCP 三次握手四次挥手浅析
大家都知道传输层中的TCP协议是面向连接的,提供可靠的连接服务,其中最出名的就是三次握手和四次挥手。 一、三次握手 三次握手的交互过程如下 喜欢钻牛角尖的我在学习三次握手的时候就想到了几个问题:为什么三次握手是三次?不是…...
【软件安装】MATLAB_R2021b for mac 安装
Mac matlab_r2021b 安装 下载链接:百度网盘 下载链接中所有文件备用。 我所使用的电脑配置: Macbook Pro M1 Pro 16512 系统 macOS 13.5 安装步骤 前置准备 无此选项者,自行百度 “mac 任何来源”。 1 下载好「MATLAB R2021b」安装文…...
电脑维护:10妙招,让你的电脑更加稳定!
你的电脑已经成为你工作、学习、娱乐的最佳工具之一,但是如果你不做好电脑维护工作,就可能面临着电脑变慢、蓝屏、崩溃等问题。在这篇文章中,我们将介绍10个电脑维护步骤,让你的电脑更加稳定! 为什么需要电脑维护&…...
大数据面试题:Kafka的单播和多播
面试题来源: 《大数据面试题 V4.0》 大数据面试题V3.0,523道题,679页,46w字 参考答案: 1、单播 一条消息只能被某一个消费者消费的模式称为单播。要实现消息单播,只要让这些消费者属于同一个消费者组即…...
python与深度学习(八):CNN和fashion_mnist二
目录 1. 说明2. fashion_mnist的CNN模型测试2.1 导入相关库2.2 加载数据和模型2.3 设置保存图片的路径2.4 加载图片2.5 图片预处理2.6 对图片进行预测2.7 显示图片 3. 完整代码和显示结果4. 多张图片进行测试的完整代码以及结果 1. 说明 本篇文章是对上篇文章训练的模型进行测…...
开发一个RISC-V上的操作系统(五)—— 协作式多任务
目录 往期文章传送门 一、什么是多任务 二、代码实现 三、测试 往期文章传送门 开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统(二)—— 系统引导程序&a…...
Mybatis-plus集合
目录 mybatis-plus集合1、简介2、特性3、开始使用4、QueryWrapper的使用5、补充 mybatis-plus集合 1、简介 MyBatis-Plus (简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 m…...
C++ 结构体和联合体
1.结构体 结构体是一种特殊形态的类,它和类一样,可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限,可以继承,支持包含多态,结构体定义的语法和类的定义语法几…...
使用TensorFlow训练深度学习模型实战(下)
大家好,本文接TensorFlow训练深度学习模型的上半部分继续进行讲述,下面将介绍有关定义深度学习模型、训练模型和评估模型的内容。 定义深度学习模型 数据准备完成后,下一步是使用TensorFlow搭建神经网络模型,搭建模型有两个选项…...
lucene、solr、es的区别以及应用场景
目录 1. Lucene:2. Solr:3. Elasticsearch: Lucene、Solr 和 Elasticsearch(ES) 都是基于 Lucene 引擎的搜索引擎,它们之间有相似之处,但也有一些不同之处。 Lucene 是一个低级别的搜索引擎库,它提供了一种用于创建和维护全文索引的 API&…...
Java方法的使用(重点:形参和实参的关系、方法重载、递归)
目录 一、Java方法 * 有返回类型,在方法体里就一定要返回相应类型的数据。没有返回类型(void),就不要返回!! * 方法没有声明一说。与C语言不同(C语言是自顶向下读取代码)&#…...
登录页的具体实现 (小兔鲜儿)【Vue3】
登录页 整体认识和路由配置 整体认识 登录页面的主要功能就是表单校验和登录登出业务 准备模板 <script setup></script><template><div><header class"login-header"><div class"container m-top-20"><h1 cl…...
大学如何自学嵌入式开发?
1. C语言:C语言是基础中的基础,刚开始学习不用太深入,一本常用的C语言的教材即可,注意不是当教科书看,而是看完一节过后,打开电脑把后面的习题都写出来,并且编译运行一遍,一定要动手…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
