天天看点

如何使用Lambda表达式重构代码

1.从匿名类到 Lambda 表达式的转换

是将实现单一抽象方法的匿名类转换为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表达式可能导致最终的代码更加晦涩。

    实际上,匿名类的类型是在初始化时确定的,而Lambda的类型取决于它的上下文。

    interface Task{ 
     public void execute(); 
    } 
    public static void doSomething(Runnable r){ r.run(); } 
    public static void doSomething(Task a){ a.execute(); } 
    
    //现在,你再传递一个匿名类实现的Task,不会碰到任何问题:
    doSomething(new Task() { 
     	public void execute() { 
     		System.out.println("Danger danger!!"); 
     	} 
    });
    
    //但是将这种匿名类转换为Lambda表达式时,就导致了一种晦涩的方法调用,
    //因为Runnable和Task都是合法的目标类型:
    doSomething(() -> System.out.println("Danger danger!!")); //麻烦来了: doSomething(Runnable) 									
    														  //和doSomething(Task)都匹配该类型
    
    //你可以对Task尝试使用显式的类型转换来解决这种模棱两可的情况:
    doSomething((Task)() -> System.out.println("Danger danger!!")); 
    //但是不要因此而放弃对Lambda的尝试。好消息是,目前大多数的集成开发环境,
    //比如NetBeans和IntelliJ都支持这种重构,它们能自动地帮你检查,避免发生这些问题。															  
               

2.从 Lambda 表达式到方法引用的转换

Lambda表达式非常适用于需要传递代码片段的场景。不过,为了改善代码的可读性,也请尽量使用方法引用。因为方法名往往能更直观地表达代码的意图。

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

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

4. 增加代码的灵活性

4.1.采用函数接口

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

4.2.有条件的延迟执行

我们经常看到这样的代码,控制语句被混杂在业务逻辑代码之中。典型的情况包括进行安全

性检查以及日志输出。比如,下面的这段代码,它使用了Java语言内置的Logger类:

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

这段代码有什么问题吗?其实问题不少。

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

更好的方案是使用log方法,该方法在输出日志消息之前,会在内部检查日志对象是否已经

设置为恰当的日志等级:

  • 这种方式更好的原因是你不再需要在代码中插入那些条件判断,与此同时日志器的状态也不再被暴露出去
  • 不过,这段代码依旧存在一个问题。不管日志器是哪个状态,日志消息总是先已构建好(即"Problem: " + generateDiagnostic()做了字符串拼接)

Lambda表达式可以解决上述问题,Lambda具有延迟执行的特点,只有在真正需要最终执行时才执行。因此,你需要做的仅仅是延迟消息构造:

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

如果日志器的级别设置恰当,log方法会在内部执行作为参数传递进来的Lambda表达式(即"Problem: " + generateDiagnostic()才进行字符串拼接,然后在输出)。这里介绍的Log方法的内部实现如下:

public void log(Level level, Supplier<String> msgSupplier){ 
	 if(logger.isLoggable(level)){ 
	 log(level, msgSupplier.get()); 
	 } 
}
           

4.3.环绕执行

如果你发现虽然你的业务代码千差万别,但是它们拥有同样的准备和清理阶段,这时,你完全可以将这部分代码用Lambda实现。

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("java8inaction/ 
	chap8/data.txt"))){ 
		return p.process(br); 
	} 
} 
public interface BufferedReaderProcessor{ 
	String process(BufferedReader b) throws IOException; 
}