2

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

1.改善代码的可读性

Java 8的新特性也可以帮助提升代码的可读性:

  • 使用Java 8,你可以减少冗长的代码,让代码更易于理解
  • 通过方法引用和Stream API,你的代码会变得更直观

这里我们会介绍三种简单的重构,利用Lambda表达式、方法引用以及Stream改善程序代码的可读性:

  • 重构代码,用Lambda表达式取代匿名类
  • 用方法引用重构Lambda表达式
  • 用Stream API重构命令式的数据处理

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

  • 在匿名类中,this代表的是类自身,但是在Lambda中,它代表的是包含类。其次,匿名类可以屏蔽包含类的变量,而Lambda表达式不
    能(它们会导致编译错误),譬如下面这段代码:

    int a = 10; 
    Runnable r1 = () -> { 
    int a = 2; //类中已包含变量a
    System.out.println(a); 
    };
  • 对于参数相同的函数式接口,调用时会造成都符合Lambda表达式的结果,不过NetBeans和IntelliJ都支持这种重构,它们能自动地帮你检查,避免发生这些问题。

3.从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; 
 }));

你可以将Lambda表达式的内容抽取到一个单独的方法中,将其作为参数传递给groupingBy
方法。变换之后,代码变得更加简洁,程序的意图也更加清晰了:

Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = 
 menu.stream().collect(groupingBy(Dish::getCaloricLevel)); 

为了实现这个方案,你还需要在Dish类中添加getCaloricLevel方法:

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; 
 } 
}
  • 除此之外,我们还应该尽量考虑使用静态辅助方法,比如comparing、maxBy。
 inventory.sort( 
 (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); 

 inventory.sort(comparing(Apple::getWeight));
  • 使用Collectors接口可以轻松得到和或者最大值,与采用Lambada表达式和底层的归约操作比起来,这种方式要直观得多.
int totalCalories = 
  menu.stream().map(Dish::getCalories) 
  .reduce(0, (c1, c2) -> c1 + c2);

int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

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());

5.增加代码的灵活性

(1)采用函数接口

用Lambda表达式带来的灵活性,它们分别是:有条件的延迟执行和环绕执行。

(2)有条件的延迟执行

如果你发现你需要频繁地从客户端代码去查询一个对象的状态,只是为了传递参数、调用该对象的一个方法(比如输出一条日志),那么可以考虑实现一个新的方法,以Lambda或者方法表达式作为参数,新方法在检查完该对象的状态之后才调用原来的方法。

(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; 
}

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

1.策略模式

策略模式代表了解决一类算法的通用解决方案,你可以在运行时选择使用哪种方案。
数字)。你可以从定义一个验证文本(以String的形式表示)的接口入手:

public interface ValidationStrategy { 
  boolean execute(String 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 Validator{ 
  private final ValidationStrategy strategy; 
  public Validator(ValidationStrategy v){ 
  this.strategy = v;
  } 
  public boolean validate(String s){ 
  return strategy.execute(s); 
  } 
} 
Validator numericValidator = new Validator(new IsNumeric()); 
boolean b1 = numericValidator.validate("aaaa"); 
Validator lowerCaseValidator = new Validator(new IsAllLowerCase ()); 
boolean b2 = lowerCaseValidator.validate("bbbb"); 

如果使用Lambda表达式,则为:

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");

2.模板方法

如果你需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进,那么采用模板方法设计模式是比较通用的方案。

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

processCustomer方法搭建了在线银行算法的框架:获取客户提供的ID,然后提供服务让用户满意。不同的支行可以通过继承OnlineBanking类,对该方法提供差异化的实现。
如果使用Lambda表达式:

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());

3.观察者模式

例子:好几家报纸机构,比如《纽约时报》《卫报》以及《世界报》都订阅了新闻,他们希望当接收的新闻中包含他们感兴趣的关键字时,能得到特别通知。

interface Observer { 
 void notify(String tweet); 
}
class NYTimes implements Observer{ 
 public void notify(String tweet) { 
 if(tweet != null && tweet.contains("money")){ 
 System.out.println("Breaking news in NY! " + tweet); 
 } 
 } 
} 
class Guardian implements Observer{ 
 public void notify(String tweet) { 
 if(tweet != null && tweet.contains("queen")){ 
 System.out.println("Yet another news in London... " + tweet); 
 } 
 } 
} 
class LeMonde implements Observer{ 
 public void notify(String tweet) { 
 if(tweet != null && tweet.contains("wine")){ 
 System.out.println("Today cheese, wine and news! " + 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)); 
 } 
}
Feed f = new Feed(); 
f.registerObserver(new NYTimes()); 
f.registerObserver(new Guardian()); 
f.registerObserver(new LeMonde()); 
f.notifyObservers("The queen said her favourite book is Java 8 in Action!");

使用Lambda表达式后,你无需显式地实例化三个观察者对象,直接传递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 another news in London... " + tweet); 
 } 
});

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); 
}
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表达式
你可以将处理对象作为函数的一个实例,或者更确切地说作为UnaryOperator<String>的一个实例。为了链接这些函数,你需要使用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?!!");

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表达式
第3章中,我们已经知道可以像引用方法一样引用构造函数。比如,下面就是一个引用贷款
(Loan)构造函数的示例:

构造器参数列表要与接口中抽象方法的参数列表一致!因此,如果构造方法中有多个参数,需要自定义函数式接口。
Supplier<Product> 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); 
}

三、测试 Lambda 表达式

  • 你可以借助某个字段访问Lambda函数
  • 要对使用Lambda表达式的方法进行测试
  • 一种策略是将Lambda表达式转换为方法引用,然后按照常规方式
  • 接受函数作为参数的方法或者返回一个函数的方法(所谓的“高阶函数”,higher-order function,我们在第14章会深入展开介绍)更难测试。如果一个方法接受Lambda表达式作为参数,你可以采用的一个方案是使用不同的Lambda表达式对它进行测试。

文中提到了List的equals方法
ArrayList、Vector两者都实现了List接口、继承AbstractList抽象类,其equals方法是在AbstractList类中定义的,源代码如下:

public boolean equals(Object o) {    
  if (o == this)        
     return true;    
  // 判断是否是List列表,只要实现了List接口就是List列表
  if (!(o instanceof List))        
     return false;    
  // 遍历list所有元素
  ListIterator<E> e1 = listIterator();
  ListIterator e2 = ((List) o).listIterator();    
  while (e1.hasNext() && e2.hasNext()) {
      E o1 = e1.next();
      Object o2 = e2.next();        
      // 有不相等的就退出
      if (!(o1==null ? o2==null : o1.equals(o2)))            
          return false;
   }    
   // 长度是否相等
   return !(e1.hasNext() || e2.hasNext());

从源码可以看出,equals方法并不关心List的具体实现类,只要是实现了List接口,并且所有元素相等、长度也相等的话就表明两个List是相等的,所以例子中才会返回true。

四、调试

1.查看栈跟踪

由于Lambda表达式没有名字,它的栈跟踪可能很难分析,编译器只能为它们指定一个名字,如果你使用了大量的类,其中又包含多个Lambda表达式,这就成了一个非常头痛的问题,这是Java编译器未来版本可以改进的一个方面。

2.使用日志调试

这就是流操作方法peek大显身手的时候。peek的设计初衷就是在流的每个元素恢复运行之前,插入执行一个动作。但是它不像forEach那样恢复整个流的运行,而是在一个元素上完成操作之后,它只会将操作顺承到流水线中的下一个操作。

List<Integer> numbers = Arrays.asList(2, 3, 4, 5);

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

输出结果:

from stream: 2 
after map: 19 
from stream: 3 
after map: 20 
after filter: 20 
after limit: 20 
from stream: 4 
after map: 21 
from stream: 5 
after map: 22 
after filter: 22 
after limit: 22

小白龙
56 声望6 粉丝