《Java8实战》第9章 重构、测试和调试

这篇具有很好参考价值的文章主要介绍了《Java8实战》第9章 重构、测试和调试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

Lambda 表达式可以帮助我们用更紧凑的方式描述程序的行为。

9.1.1 改善代码的可读性

可读性非常主观,但是通俗的理解就是“别人理解这段代码的难易程度”。
改善可读性意味着你要确保你的代码能非常容易地被包括自己在内的所有人理解和维护。
使用 Java 8,你可以减少冗长的代码,让代码更易于理解。
使用lambda的三个简单的重构点:

  • 重构代码,用 Lambda 表达式取代匿名类;
  • 用方法引用重构 Lambda 表达式;
  • 用 Stream API 重构命令式的数据处理。
9.1.2 从匿名类到 Lambda 表达式的转换
传统方式的匿名内部类
Runnable r1 = new Runnable(){ 
 public void run(){ 
   System.out.println("Hello"); 
 } 
}; 

新的方式
Runnable r2 = () -> System.out.println("Hello"); 

但是在某些情况下,将匿名类转换为 Lambda 表达式可能是一个比较复杂的过程 。① 首先,匿名类和 Lambda 表达式中的 this 和 super 的含义是不同的。在匿名类中,this 代表的是类自身,但是在 Lambda 中,它代表的是包含类。其次,匿名类可以屏蔽包含类的变量,而 Lambda表达式不能(它们会导致编译错误),譬如下面这段代码:

int a = 10; 
Runnable r1 = () -> { 
 int a = 2; // 编译错误
 System.out.println(a); 
};

Runnable r2 = new Runnable(){ 
 public void run(){ 
   int a = 2; // 正常
   System.out.println(a); 
 } 
}; 

匿名类的类型是在初始化时确定的,而 Lambda 的类型取决于它的上下文,这样可能会使代码更加晦涩。
比如这样:

interface Task{ 
 public void execute(); 
} 
public static void doSomething(Runnable r){ r.run(); } 
public static void doSomething(Task a){ a.execute(); }
如果使用的是匿名内部类,那么一看就知道使用了什么哪个参数类型
doSomething(new Task() { 
 public void execute() { 
   System.out.println("Danger danger!!"); 
 } 
});
如果使用lambda的话,你就分不清究竟使用的是哪个类型了
doSomething(() -> System.out.println("Danger danger!!"));
不过也可以使用显式的类型来调用
doSomething((Task)() -> System.out.println("Danger danger!!")); 
9.1.3 从 Lambda 表达式到方法引用的转换

Lambda 表达式非常适用于需要传递代码片段的场景。但是为了代码的可读性,尽量使用方法引用。

Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = 
 menu.stream() 
 .collect( 
 groupingBy(dish -> { 
     if (dish.getCalories() <= 400) return CaloricLevel.DIET; 
     else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; 
     else return CaloricLevel.FAT; 
 })); 

可以修改成
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = 
 menu.stream().collect(groupingBy(Dish::getCaloricLevel)); 

把原来的判断代码封装到getCaloricLevel。
很多求和统计可以直接使用函数添加。

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

命令式代码使用了两种模式:筛选和抽取,这两种模式被混在了一起,这样的代码结构迫使程序员必须彻底搞清楚程序的每个细节才能理解代码的功能。

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

现在的模式
menu.parallelStream() 
 .filter(d -> d.getCalories() > 300) 
 .map(Dish::getName) 
 .collect(toList()); 

将命令式的代码结构转换为 Stream API 的形式是个困难的任务,因为你需要考虑控制流语句,比如 break、continue 和 return,并选择使用恰当的流操作。不过已经有一些工具,比如 LambdaFicator

9.1.5 增加代码的灵活性
  1. 采用函数接口

没有函数接口,就无法使用 Lambda 表达式。因此,你需要在代码中引入函数接口。

  1. 有条件的延迟执行

输出日志的时候,先进行日志级别的判断

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

上面代码问题:

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

更好的方案是使用 log 方法,该方法在输出日志消息之前,会在内部检查日志对象是否已经设置为恰当的日志等级:
logger.log(Level.FINER, "Problem: " + generateDiagnostic());
但是这样子还是需要去判断日志的等级。
java8引入了一个对 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 或者方法引用作为参数,新方法在检查完该对象的状态之后才调用原来的方法。你的代码会因此而变得更易读(结构更清晰),封装性更好(对象的状态也不会暴露给客户端代码了)。

  1. 环绕执行

第3章讲过,就是前后的代码都是相同的,但是中间的代码不同,使用这种模式,可以减少代码的冗余。

String oneLine = processFile((BufferedReader b) -> b.readLine()); 
String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine()); 
public static String processFile(BufferedReaderProcessor p) throws 
 IOException { 
 try(BufferedReader br = new BufferedReader(new FileReader("ModernJavaInAction/chap9/data.txt"))) { 
   return p.process(br);// 将 BufferedReaderProcessor作为执行参数传入
 } 
} 
public interface BufferedReaderProcessor {
// 使用 Lambda 表达式的函数接口,该接口能够抛出一个 IOException
 String process(BufferedReader b) throws IOException; 
} 

9.2 使用 Lambda 重构面向对象的设计模式

9.2.1 策略模式

之前就了解过,根据苹果的重量或者颜色来筛选。
策略模式包含三部分内容:

  • 一个代表某个算法的接口(Strategy 接口)
  • 一个或多个该接口的具体实现,它们代表了算法的多种实现(比如,实体类ConcreteStrategyA或者 ConcreteStrategyB)
  • 一个或多个使用策略对象的客户

《Java8实战》第9章 重构、测试和调试

普通情况下,就是定义一个接口,方法,然后就写几个实现类去实现。
使用lambda表达式就可以直接传递行为

9.2.2 模板方法

模板方法模式在你“希望使用这个算法,但是需要对其中的某些行进行改进,才能达到希望的效果”时是非常有用的。
不同分行的在线银行应用让客户满意的方式可能略有不同,比如给客户的账户发放红利,或者仅仅是少发送一些推广文件。

abstract class OnlineBanking { 
 public void processCustomer(int id){ 
   Customer c = Database.getCustomerWithId(id); 
   makeCustomerHappy(c); 
 } 
 abstract void makeCustomerHappy(Customer c); 
}

使用lambda表达式
这里我们向 processCustomer 方法引入了第二个参数,它是一个 Consumer类型的参数,与前文定义的 makeCustomerHappy 的特征保持一致:

public void processCustomer(int id, Consumer<Customer> makeCustomerHappy){ 
 Customer c = Database.getCustomerWithId(id); 
 makeCustomerHappy.accept(c); 
} 

调用:
new OnlineBankingLambda().processCustomer(1337, (Customer c) -> System.out.println("Hello " + c.getName());

9.2.3 观察者模式

某些事件发生时(比如状态转变),如果一个对象(通常称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案。
可以先
使用 Lambda 表达式
Observer 接口的所有实现类都提供了一个方法:notify。新闻到达时,它们都只是对同一段代码封装执行。Lambda 表达式的设计初衷就是要消除这样的僵化代码。

f.registerObserver((String tweet) -> { 
 if(tweet != null && tweet.contains("money")){ 
   System.out.println("Breaking news in NY! " + tweet); 
 } 
}); 
f.registerObserver((String tweet) -> { 
 if(tweet != null && tweet.contains("queen")){ 
   System.out.println("Yet more news from London... " + tweet); 
 } 
});
9.2.4 责任链模式

责任链模式是一种创建处理对象序列(比如操作序列)的通用方案。一个处理对象可能需要在完成一些工作之后,将结果传递给另一个对象,这个对象接着做一些工作,再转交给下一个处理对象,以此类推。

public abstract class ProcessingObject<T> { 
 protected ProcessingObject<T> successor; 
 public void setSuccessor(ProcessingObject<T> successor){ 
   this.successor = successor; 
 } 
 public T handle(T input){ 
   T r = handleWork(input); 
   if(successor != null){ 
     return successor.handle(r); 
   } 
   return r; 
 } 
 abstract protected T handleWork(T input); 
} 

《Java8实战》第9章 重构、测试和调试

public class HeaderTextProcessing extends ProcessingObject<String> { 
 public String handleWork(String text){ 
   return "From Raoul, Mario and Alan: " + text; 
 } 
} 
public class SpellCheckerProcessing extends ProcessingObject<String> { 
 public String handleWork(String text){ 
   return text.replaceAll("labda", "lambda"); 
 } 
} 

ProcessingObject<String> p1 = new HeaderTextProcessing(); 
ProcessingObject<String> p2 = new SpellCheckerProcessing(); 
p1.setSuccessor(p2); 
String result = p1.handle("Aren't labdas really sexy?!!"); 
System.out.println(result); 

使用 Lambda 表达式
这个模式看起来像是在链接(也就是构造)函数。你可以将处理对象作为 Function<String, String>的一个实例,或者更确切地说作为UnaryOperator的一个实例。为了链接这些函数,你需要使用 andThen 方法对其进行构造。

UnaryOperator<String> headerProcessing = 
 (String text) -> "From Raoul, Mario and Alan: " + text; 
UnaryOperator<String> spellCheckerProcessing = 
 (String text) -> text.replaceAll("labda", "lambda"); 
Function<String, String> pipeline = 
 headerProcessing.andThen(spellCheckerProcessing); 
String result = pipeline.apply("Aren't labdas really sexy?!!"); 
9.2.5 工厂模式

使用工厂模式,你无须向客户暴露实例化的逻辑就能完成对象的创建。

public class ProductFactory { 
 public static Product createProduct(String name){ 
 switch(name){ 
   case "loan": return new Loan(); 
   case "stock": return new Stock(); 
   case "bond": return new Bond(); 
   default: throw new RuntimeException("No such product " + name); 
 } 
 } 
}

使用
Product p = ProductFactory.createProduct("loan");
使用 Lambda 表达式
Supplier loanSupplier = Loan::new;
Loan loan = loanSupplier.get();
通过这种方式,你可以重构之前的代码,创建一个 Map,将产品名映射到对应的构造函数:

final static Map<String, Supplier<Product>> map = new HashMap<>(); 
static { 
 map.put("loan", Loan::new); 
 map.put("stock", Stock::new); 
 map.put("bond", Bond::new); 
} 

你可以像之前使用工厂设计模式那样,利用这个 Map 来实例化不同的产品
public static Product createProduct(String name){ 
 Supplier<Product> p = map.get(name); 
 if(p != null) return p.get(); 
 throw new IllegalArgumentException("No such product " + name); 
}

9.3 测试 Lambda 表达式

9.4 调试

因为 Lambda 表达式没有名字,涉及 Lambda 表达式的栈跟踪可能非常难理解。这是 Java 编译器未来版本可以改进的一个方面。
日志调试可以使用peek
《Java8实战》第9章 重构、测试和调试文章来源地址https://www.toymoban.com/news/detail-422987.html

List<Integer> result = 
 numbers.stream() 
// 输出来自数据源的当前元素值
 .peek(x -> System.out.println("from stream: " + x)) 
 .map(x -> x + 17) 
// 输出 map 操作的结果
 .peek(x -> System.out.println("after map: " + x)) 
 .filter(x -> x % 2 == 0) 
// 输出经过 filter 操作之后,剩下的元素个数
 .peek(x -> System.out.println("after filter: " + x)) 
 .limit(3) 
// 输出经过 limit 操作之后,剩下的元素个数
 .peek(x -> System.out.println("after limit: " + x)) 
 .collect(toList()); 

9.5 小结

  • Lambda 表达式能提升代码的可读性和灵活性。
  • 如果你的代码中使用了匿名类,那么尽量用 Lambda 表达式替换它们,但是要注意二者间语义的微妙差别,比如关键字 this,以及变量隐藏。
  • 跟 Lambda 表达式比起来,方法引用的可读性更好。
  • 尽量使用 Stream API 替换迭代式的集合处理。
  • Lambda 表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,典型的比如策略模式、模板方法、观察者模式、责任链模式,以及工厂模式。
  • 即使采用了 Lambda 表达式,也同样可以进行单元测试,但是通常你应该关注使用了Lambda 表达式的方法的行为。
  • 尽量将复杂的 Lambda 表达式抽象到普通方法中。
  • Lambda 表达式会让栈跟踪的分析变得更为复杂。
  • 流提供的 peek 方法在分析 Stream 流水线时,能将中间变量的值输出到日志中,是非常有用的工具。

到了这里,关于《Java8实战》第9章 重构、测试和调试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何编写可读性高的 C 代码?

    目录 1.引言 2.基础知识 3.面向对象语言的特性 4.C 语言的面向对象 5.测试 6.总结         面向对象的语言更接近人的思维方式,而且在很大程度上降低了代码的复杂性,同时提高了代码的可读性和可维护性,传统的 C 代码同样可以设计出比较易读,易维护,复杂度较低的优

    2024年04月22日
    浏览(35)
  • 代码可读性艺术在Andorid中的体现

    前言 最近接手的一些项目,不同的人编码风格迥异,类里的变量、方法的定义穿插,注释极为稀少,更有一些变量和方法的命名非常近似,例如表示播放队列的\\\"playQueue\\\"和表示歌单的\\\"playList\\\",wtf? 这不是一个意思吗?一些回调的时机也不能直观的看出来,通常需要debug调试多次;multi proj

    2024年02月03日
    浏览(35)
  • 提高代码可读性和可维护性的命名建议

    当进行接口自动化测试时,良好的命名可以提高代码的可读性和可维护性。以下是一些常用的命名建议: 变量和函数命名: 使用具有描述性的名称,清晰地表达变量或函数的用途和含义。 使用小写字母和下划线来分隔单词,例如  login_url 、 send_request 。 避免使用单个字符或

    2024年02月10日
    浏览(45)
  • 50个简洁的提示提高代码可读性和效率(0-10)

    这篇文章整理了50个简洁的提示,可以提高您的代码可读性和效率。这些提示来自个人项目、彻底的代码审查和与资深开发人员的启发性讨论。 无论您是新手还是经验丰富的开发人员,这篇文章都应该能够帮助您学到一些东西。 这个列表包括常见的Python模式、核心概念和最佳

    2024年02月10日
    浏览(45)
  • 编写魅力十足的代码:优化可读性、维护性和性能的关键

    本篇汇总了平时在工作开发中常遇到的业务逻辑的优雅写法,也汇总了自己还是新人时,拿到一个业务不知道怎么下手的痛点,依稀记得那时候总感觉自己写的代码不规范。 写完之后,感觉还是美好的,又学到东西了。 采用简洁的语法和结构,遵循一致的命名规范,具有良

    2024年02月10日
    浏览(55)
  • 炫技亮点 使用Optional类优化代码,提升可读性和简化空值处理

    在日常的软件开发中,我们经常需要处理可能为空的值,例如 从数据库查询数据 、 调用外部接口获取数据 、 从配置文件读取配置项 等。传统的处理方式往往需要使用 繁琐的空值判断和异常处理 代码,使得代码变得冗长和难以理解。为了解决这个问题,Java 8 引入了 Optio

    2024年02月13日
    浏览(50)
  • chatgpt赋能python:Python如何分行——提高代码可读性和效率的必备技能

    分行,即将一行长代码分为多行,使得代码更加易读、易维护、易修改。 Python作为一门高级编程语言,具有简洁、易读、高效的特点。但在实际编程过程中,难免会遇到较长的代码行,导致代码可读性下降,不利于程序员的开发和维护。因此,Python中分行技术就显得尤为重要

    2024年02月08日
    浏览(41)
  • 如何修改min.js或者压缩后的js,以便提高代码的可读性。

    前端的js上线的时候一般会使用打包工具处理(webpack,gulp,ugly.js 等)。这样做有几点作用。 可以压缩空间,提高页面响应速度 一定程度上可以保护自己的代码安全,防止别人清晰看懂逻辑或者拷贝代码。 提高别人阅读自己代码的门槛 可前端开发工作中多多少少,会需要看

    2024年02月11日
    浏览(42)
  • 【Spring MVC】获取 @RequsetBody 标识的对象,使用适配器模式增加代码可读性

    一个技术需求引发的思考和实践: 思考 用 AOP 把校验代码 实践 用 Spring MVC 的 RequestBodyAdvice 做AOP逻辑 继承 RequestBodyAdviceAdapter 实现自己的 适配器 用自己的适配器让代码可读性增加 熟悉 Spring MVC 、Java 反射的一些实践 本文内容 澄清一个AOP校验JSON内容的思路 复习适配器模式

    2024年02月10日
    浏览(41)
  • chatgpt赋能python:Python分组:组织你的代码,提升可读性和可维护性

    在编写代码时,组织良好的代码结构和架构是非常重要的。对于大规模的项目,特别是多人合作开发的项目来说,代码管理和组织是至关重要的。Python 分组是一种常用的技术,可以帮助我们组织代码并提高代码的可读性和可维护性。 Python 分组指的是将一段代码按照一定的逻

    2024年02月06日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包