Java8实战-总结32

重构、测试和调试

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

从命令式的数据处理切换到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对象进行处理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值