《Java8实战》读书笔记07:Lambda 重构测试和调试(设计模式实现)
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java8实战》读书笔记07:Lambda 重构测试和调试(设计模式实现)相关的知识,希望对你有一定的参考价值。
《Java8实战》读书笔记07:Lambda 重构、测试和调试
第8章 重构、测试和调试
本章内容
如何
使用Lambda
表达式重构代码
Lambda
表达式对面向对象的设计模式
的影响
Lambda
表达式的测试
如何调试使用Lambda
表达式和Stream API
的代码
Lambda
可用来简化匿名类
的废话代码。(比如:核心代码就一句话,为了这一句,以前非得让我实例化一个匿名类再重写方法,太多无用功。)- 使用
Lambda
表达式重构代码
的路基就是换掉匿名类
。
重构设计模式方面比如:策略、模板
这些lambda
天生就可以延迟执行。 - 注意:我们重构的目的是让代码更简洁直观,这也是我们要不要把
匿名类
换成Lambda
的依据。(如果重构后,导致逻辑更绕,阅读成本增加就没必要了。)
8.1 为改善可读性和灵活性重构代码
8.1.1 改善代码的可读性
为了确保你的代码能被其他人理解,有几个步骤可以尝试,比如确保你的代码附有良好的文档,并严格遵守编程规范。
- 跟之前的版本相比较,Java 8的新特性也可以帮助提升代码的可读性:
- 使用Java 8,你可以减少冗长的代码,让代码更易于理解
- 通过
方法引用
和Stream API
,你的代码会变得更直观。这里我们会介绍三种
简单的重构,利用Lambda表达式
、方法引用
以及Stream
改善程序代码的。
- 可读性:
- 重构代码,用
Lambda
表达式取代匿名类
- 用
方法引用
重构Lambda
表达式- 用
Stream API
重构命令式的数据处理
8.1.2 从匿名类到 Lambda 表达式的转换
- 匿名类
Runnable r1 = new Runnable()
public void run()
System.out.println("Hello 匿名类");
;
new Thread(r1).start();
- Lambda
Runnable r2 = () -> System.out.println("Hello Lambda");
new Thread(r2).start();
Lambda
是表达式
,匿名类
是类
。对此保持清醒的认识,关于作用域
、this
的问题就不言自明了。Lambda
的类型
根据上下文决定。如果出现重载
,可以显示转换
类型。例子:
(IntelliJ支持这种重构,能自动帮你检查)
interface Task
public void execute();
public static void doSomething(Runnable r) r.run();
public static void doSomething(Task a) a.execute();
// 这样 doSomething 就知道自己是谁了
doSomething((Task)() -> System.out.println("Danger danger!!"));
8.1.3 从 Lambda 表达式到方法引用的转换
这是一段Lambda
实现的分组逻辑。将groupingBy
内的业务代码提取出来改为方法引用
可以让代码简洁
、意图清晰
。
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;
)
);
在Dish
类中添加一个方法getCaloricLevel
来实现groupingBy
中的逻辑。
public class Dish
// …
public CaloricLevel getCaloricLevel()
if (this.getCalories() <= 400) return CaloricLevel.DIET;
else if (this.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
有了Dish::getCaloricLevel
原来的代码就可以简化为如下效果:(取菜单流》按热量级别分组》收集到Map。代码简洁意图清晰,简直像读小说)
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream()
.collect(groupingBy(Dish::getCaloricLevel));
JDK提供了大量现成工具:java.util.stream.Collectors 收集器、java.util.Comparator 比较器。《Java8实战》读书笔记04、《Java8实战》读书笔记05 有相关介绍。
8.1.4 从命令式的数据处理切换到 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());
哪个好阅读一目了然。但目前还有个问题就是不支持:break、continue、return
书上给了个连接说是辅助工具,但是无法访问 http://refactoring.info/tools/LambdaFicator/。
个人认为换个思路:break、continue
可以通过 filter、limit
等配合实现。return
我只想到能抛异常模拟。
8.1.5 增加代码的灵活性
本节介绍了具体的方法:
1. 采用函数接口
将参数类型声明为函数接口
,这样就能能给它传Lambda
了。然而何时需要这样做呢?引出了两个概念:条件的延迟执行
和环绕执行
。(本质就是TemplateMethod模板方法的思想)
2. 条件的延迟执行(别被名字唬了)
客户代码原本是每次输出日志,先判断,再打印。
if (logger.isLoggable(Log.FINER))
logger.finer("Problem: " + generateDiagnostic());
提取为一个log
函数后,每次调用,传入条件 level
、行为 msgSupplier
。条件满足,就执行。
public void log(Level level, Supplier<String> msgSupplier)
if(logger.isLoggable(level))
log(level, msgSupplier.get());
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
3. 环绕执行(别被名字唬了)
processFile
可以接收一个Lambda
,并在创建BufferedReader
后用这个Lambda
来处理它。
public static String processFile(BufferedReaderProcessor p) throws IOException
try(BufferedReader br = new BufferedReader(new FileReader("src\\\\main\\\\resources\\\\test.txt")))
return p.process(br);
public interface BufferedReaderProcessor
String process(BufferedReader b) throws IOException;
public static void main(String[] args) throws IOException
String oneLine = processFile((BufferedReader b) -> b.readLine());
System.out.println("oneLine: " + oneLine);
String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine());
System.out.println("twoLines: " + twoLines);
8.2 使用 Lambda 重构面向对象的设计模式
8.2.1 策略模式
准备工作
准备:
策略接口 ValidationStrategy
应用类 Validator
// 定义策略接口(只有一个方法,所以它也是一个函数式接口)
public interface ValidationStrategy
boolean execute(String s);
// 构造时指定策略,validate 执行策略返回结果。
public class Validator
private final ValidationStrategy strategy;
public Validator(ValidationStrategy v)
this.strategy = v;
public boolean validate(String s)
return strategy.execute(s);
传统写法
// 实现策略 一
public class IsAllLowerCase implements ValidationStrategy
public boolean execute(String s)
return s.matches("[a-z]+");
// 实现策略 二
public class IsNumeric implements ValidationStrategy
public boolean execute(String s)
return s.matches("\\\\d+");
// 客户端代码
public class LambdaDemo
public static void main(String[] args)
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaaa");
Validator lowerCaseValidator = new Validator(new IsAllLowerCase ());
boolean b2 = lowerCaseValidator.validate("bbbb");
Lambda
写法
省了策略实现类
,直接 new Validator
策略传Lambda
就OK了。
public static void main(String[] args)
Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaaa");
Validator lowerCaseValidator = new Validator((String s) -> s.matches("\\\\d+"));
boolean b2 = lowerCaseValidator.validate("bbbb");
8.2.2 模板方法
如果你需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进,
那么采用模板方法设计模式是比较通用的方案。
准备工作
- 消费者 Customer
@Data
@AllArgsConstructor
public class Customer
private int id;
private String name;
传统写法
public abstract class OnlineBanking
public void processCustomer(int id)
Customer c = Database.getCustomerWithId(id); // 从数据库取
makeCustomerHappy(c);
abstract void makeCustomerHappy(Customer c);
public class BankA extends OnlineBanking
@Override
void makeCustomerHappy(Customer c)
System.out.println(String.format("ID: %d - %s 你好!", c.getId(), c.getName()));
客户端代码
public class LambdaDemo
public static void main(String[] args)
BankA bankA = new BankA();
bankA.processCustomer(666);
// ID: 666 - 笨笨 你好!
Lambda
写法
改造processCustomer
加一个参数,类型为函数式接口Consumer<T>
public class OnlineBankingLambda
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy)
Customer c = Database.getCustomerWithId(id); // 从数据库取
makeCustomerHappy.accept(c);
使用时,第二个参数传Lambda
public class LambdaDemo
public static void main(String[] args)
OnlineBankingLambda onlineBankingLambda = new OnlineBankingLambda();
onlineBankingLambda.processCustomer(666, c ->
System.out.println(String.format("ID: %d - %s 你好!", c.getId(), c.getName()));
);
8.2.3 观察者模式
观察者模式是一种比较常见的方案,某些事件发生时(比如状态转变),如果一个对象(通
常我们称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案。创建图形用户界面(GUI)程序时,你经常会使用该设计模式。这种情况下,你会在图形用户界面组件(比如按钮)上注册一系列的观察者。如果点击按钮,观察者就会收到通知,并随即执行某个特定的行为。 但是观察者模式并不局限于图形用户界面。比如,观察者设计模式也适用于股票交易的情形,多个券商可能都希望对某一支股票价格(主题)的变动做出响应。
书上写有的点多,我再简化一下。
准备工作
- 观察者接口 Observer
- 主题接口 Subject
- 主题实现 Feed
// 观察者接口
interface Observer
void notify(String tweet);
// 主题接口
interface Subject
void registerObserver(Observer o); // 注册观察者
void notifyObservers(String tweet); // 广播通知所有观察者
// 主题实现
class Feed implements Subject
private final List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer o)
this.observers.add(o);
public void notifyObservers(String tweet)
observers.forEach(o -> o.notify(tweet));
传统写法
实现 N 个具体的观察者。(书上还对消息内容做了判断,满足条件的消息,才处理。我这里简化了)
// 观察者 A
class ObserverA implements Observer
public void notify(String tweet)
System.out.println("观察者A 收到通知:" + tweet);
// 观察者 B
class ObserverB implements Observer
public void notify(String tweet)
System.out.println("观察者B 收到通知:" + tweet);
// 观察者 C
class ObserverC implements Observer
public void notify(String tweet)
System.out.println("观察者C 收到通知:" + tweet);
客户端代码
public static void main(String[] args)
Feed f = new Feed();
f.registerObserver(new ObserverA());
f.registerObserver(new ObserverB());
f.registerObserver(new ObserverC());
f.notifyObservers("大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!");
Lambda
写法
无需实现几个类来用。直接传Lambda
。
public static void main(String[] args)
Feed f = new Feed();
f.registerObserver(tweet -> System.out.println("观察者A 收到通知:" + tweet));
f.registerObserver(tweet -> System.out.println("观察者A 收到通知:" + tweet));
f.registerObserver(tweet -> System.out.println("观察者A 收到通知:" + tweet));
f.notifyObservers("大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!");
8.2.4 责任链模式
责任链模式是一种创建处理对象序列(比如操作序列)的通用方案。一个处理对象可能需要
在完成一些工作之后,将结果传递给另一个对象,这个对象接着以上是关于《Java8实战》读书笔记07:Lambda 重构测试和调试(设计模式实现)的主要内容,如果未能解决你的问题,请参考以下文章
《Java8实战》 - 读书笔记 - Lambda 表达式的组合用法