Java8实战-总结32

2023-09-20 23:31:58

重构、测试和调试

为改善可读性和灵活性重构代码

从命令式的数据处理切换到Stream

建议将所有使用迭代器这种数据处理模式处理集合的代码都转换成Stream API的方式。Stream API能更清晰地表达数据处理管道的意图。除此之外,通过短路和延迟载入以及利用现代计算机的多核架构,我们可以对Stream进行优化。

比如,下面的命令式代码使用了两种模式:筛选和抽取,这两种模式被混在了一起,这样的代码结构迫使程序员必须彻底搞清楚程序的每个细节才能理解代码的功能。此外,实现需要并行运行的程序所面对的困难也多得多:

	List<String> dishNames = new ArrayList<>();
	for(Dish dish: menu) {
		if(dish.getcalories() > 300) {
			dishNames.add(dish.getName());	
		}
	}

替代方案使用Stream API,采用这种方式编写的代码读起来更像是问题陈述,并行化也非常容易:

menu.parallelStream()
	.filter(d -> d.getcalories() > 300)
	.map(Dish::getName)
	.collect(toList());

将命令式的代码结构转换为Stream API的形式是个困难的任务,因为需要考虑控制流语句,比如breakcontinuereturn,并选择使用恰当的流操作。

增加代码的灵活性

Lambda表达式有利于行为参数化。可以使用不同的Lambda表示不同的行为,并将它们作为参数传递给函数去处理执行。这种方式可以从容地面对需求的变化。比如,可以用多种方式为Predicate创建筛选条件,或者使用Comparator对多种对象进行比较。现在,来看看哪些模式可以马上应用到现有的代码中,享受Lambda表达式带来的便利。

  1. 采用函数接口

首先,没有函数接口,就无法使用Lambda表达式。因此,需要在代码中引入函数接口。在什么情况下使用它们呢?这里介绍两种通用的模式,可以依照这两种模式重构代码,利用Lambda表达式带来的灵活性,它们分别是:有条件的延迟执行和环绕执行。

  1. 有条件的延迟执行

经常看到这样的代码,控制语句被混杂在业务逻辑代码之中。典型的情况包括进行安全性检查以及日志输出。比如,下面的这段代码,它使用了Java语言内置的Logger类:

if (logger.isLoggable(Log.FINER)) {
	logger.finer("Problem:" + generateDiagnostic());
}

这段代码的问题不少:

  • 日志器的状态(它支持哪些日志等级)通过isLoggable方法暴露给了客户端代码。
  • 为什么要在每次输出一条日志之前都去查询日志器对象的状态,这只能搞砸代码。

更好的方案是使用log方法,该方法在输出日志消息之前,会在内部检查日志对象是否已经设置为恰当的日志等级:

logger.log(Leve1.FINER, "Problem:" + generateDiagnostic());

这种方式更好的原因是不再需要在代码中插入那些条件判断,与此同时日志器的状态也不再被暴露出去。不过,这段代码依旧存在一个问题。日志消息的输出与否每次都需要判断,即使你已经传递了参数,不开启日志。

这就是Lambda表达式可以施展拳脚的地方。你需要做的仅仅是延迟消息构造,如此一来,日志就只会在某些特定的情况下才开启(以此为例,当日志器的级别设置为FINER时)。显然,Java 8API设计者们已经意识到这个问题,并由此引入了一个对log方法的重载版本,这个版本的log方法接受一个Supplier作为参数。这个替代版本的log方法的函数签名如下:

public void log(Level level, Supplier<String> msgSupplier)

可以通过下面的方式对它进行调用:

logger.log(Level.FINER, () -> "Problem:" + generateDiagnostic());

如果日志器的级别设置恰当,log方法会在内部执行作为参数传递进来的Lambda表达式。这里介绍的Log方法的内部实现如下:

public void log(Level level, Supplier<String> msgSupplier) {
	if(logger.isLoggable(level)) {
		log(level, msgSupplier.get()); //执行Lambda表达式
	}
}

如果你发现你需要频繁地从客户端代码去查询一个对象的状态(比如前文例子中的日志器的状态),只是为了传递参数、调用该对象的一个方法(比如输出一条日志),那么可以考虑实现一个新的方法,以Lambda或者方法表达式作为参数,新方法在检查完该对象的状态之后才调用原来的方法。你的代码会因此而变得更易读(结构更清晰),封装性更好(对象的状态也不会暴露给客户端代码了)。

  1. 环绕执行

如果发现虽然你的业务代码千差万别,但是它们拥有同样的准备和清理阶段,这时,完全可以将这部分代码用Lambda实现。这种方式的好处是可以重用准备和清理阶段的逻辑,减少重复冗余的代码。下面这段代码,在打开和关闭文件时使用了同样的逻辑,但在处理文件时可以使用不同的Lambda进行参数化。

String oneLine = processFile((BufferedReader b) -> b.readLine()); //传入一个Lambda表达式

String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine()); //传入另一个Lambda表达式

public static String processFile(BufferedReaderProcessor p) throws IOException {
	try(BufferedReader br = new BufferedReader(new FileReader("javasinaction/chap8/data.txt"))) {
		return p.process(br); //将BufferedReaderProcessor作为执行参数传入

public interface BufferedReaderProcessor { 使用Lambda表达式的函数接口,该接口能够抛出一个IOException
	String process(BufferedReader b) throws IOException;
}

这一优化是凭借函数式接口BufferedReaderProcessor达成的,通过这个接口,可以传递各种Lambada表达式对BufferedReader对象进行处理。

更多推荐

企业该如何选择数字化转型工具?_光点科技

随着科技的不断进步和数字化的浪潮席卷全球,企业数字化转型已经成为了保持竞争力和持续增长的关键因素之一。无论企业规模大小,数字化转型都可以提高效率、降低成本、改善客户体验,从而实现更好的业务结果。然而,要成功进行数字化转型,企业首先需要选择适合自己的数字化工具。1.明确数字化转型目标在选择数字化转型工具之前,企业需要明确

【RocketMQ】路由中心NameServer

【RocketMQ】路由中心NameServer参考资料:RocketMQNameserver背后的设计理念RocketMQ之NameServer详解深入剖析RocketMQ源码-NameServer——vivo互联网技术《RocketMQ技术内幕》文章目录【RocketMQ】路由中心NameServerNameSer

java接入烽火科技拾音器详细步骤

1背景项目中需要拾音器去采集音频数据并保存成mp3这种音频文件,以便以后如果有纠纷后可以作为证据去减少纠纷,于是采购了一台烽火科技的拾音器设备,包括一个采音器及一个处理终端。2接线设备拿过来第一件事是接线,通电,让设备运行起来。采音器一共有三根线,红、黑、白,白线接音频输入端R、黑线接音频输入端的G、红色接音频输入端的

GaussDB技术解读系列:运维自动驾驶探索

近日,在第14届中国数据库技术大会(DTCC2023)的GaussDB“五高两易”核心技术,给世界一个更优选择专场,华为云数据库运维研发总监李东详细解读了GaussDB运维系统自动驾驶探索和实践。随着企业数字化转型进入深水区,数据库系统越来越复杂,运维团队维护的数据库规模越来越大,传统工具化的运维已无法满足当前运维的要

PHP初中高级1000道面试题大全(持续更新中50/1000)

目录1、echo(),print(),print_r(),var_dump()的区别?2、表单中get与post提交方法的区别?3、session与cookie的区别?4、请说明PHP中传值与传引用的区别。什么时候传值什么时候传引用?5、请解释PHP中的PDO是什么?6、请解释PHP中的抽象类和接口的区别是什么?7、请

一、Java面试题基础第十天

一、Java面试题基础第十天1.什么是反射?Java是一门静态语言,它通过编译以后才能执行的编程语言,但是可以通过反射使Java成为一个准动态语言,Java在运行过程中动态获取获取对象的属性,调用它的方法,就叫做反射2.反射有哪些应用场景呢?1.jdbc连接数据库的时候加载驱动时Class.forName()2.Jav

Webpack 热更新原理

什么是热更新模块热替换(hotmodulereplacement或HMR)是webpack提供的最有用的功能之一。它允许在运行时更新所有类型的模块,而无需完全刷新一般的刷新我们分两种:一种是页面刷新,不保留页面状态,就是简单粗暴,直接window.location.reload()。另一种是基于WDS(Webpack-

【C++从0到王者】第三十三站:AVL树

文章目录前言一、AVL树的概念二、AVL树的实现1.AVL树的结点定义2.AVL树的插入之插入部分3.AVL树的插入之平衡因子的改变4.AVL树的插入之左旋5.AVL树的左旋抽象图6.AVL树的右旋抽象图7.AVL树的双旋8.AVL树的右左双旋9.AVL树的右左双旋的本质10.AVL树的左右双旋11.AVL树的验证12

opencv 图像的缩放(放大,缩小),翻转,旋转

文章目录opencv图像的缩放(放大,缩小),翻转,旋转1、图像的缩放,旋转过程中为什么需要插值:2、常见的插值算法包括:3、图像的缩放,翻转,旋转:(1)图像的缩放cv2::resize(),用于改变图像大小的函数,它可以用于图像的放大、缩小操作:函数原型:示例:将一个图像缩小为原来的一半(2)图像的翻转cv2::f

javaee之黑马乐优商城5

分析一下spu与sku的数据结构再来说一下什么是spustandardproductunit标准产品单元:SPU级别的规格参数通常是与整个产品类型或产品系列相关的通用参数。比如华为手机下面的p系列、荣耀系列,都可以标识为spu级别规格参数skustockkeepingunit库存保管单位:SKU级别的规格参数是具体到每

Git基础操作

前言本文会向您介绍如何安装git,以及快速地上手add,commit,push,版本回退操作基础配置关于windous上的安装git官网已经介绍的很清楚了,您可以直接点入链接windows安装如果你的平台是centos,以centos7.6为例:⾸先,你可以试着输⼊git,看看系统有没有安装Git:-bash:git:

热文推荐