Java 8的新特性也能够帮助提高代码的可读性:java
这里咱们会介绍三种简单的重构,利用Lambda表达式、方法引用以及Stream改善程序代码的可读性:算法
在匿名类中,this表明的是类自身,可是在Lambda中,它表明的是包含类。其次,匿名类能够屏蔽包含类的变量,而Lambda表达式不
能(它们会致使编译错误),譬以下面这段代码:设计模式
int a = 10; Runnable r1 = () -> { int a = 2; //类中已包含变量a System.out.println(a); };
按照食物的热量级别对菜肴进行分类:app
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));
原来:测试
List<String> dishNames = new ArrayList<>(); for(Dish dish: menu){ if(dish.getCalories() > 300){ dishNames.add(dish.getName()); } }
替换成流式:this
menu.parallelStream() .filter(d -> d.getCalories() > 300) .map(Dish::getName) .collect(toList());
用Lambda表达式带来的灵活性,它们分别是:有条件的延迟执行和环绕执行。设计
若是你发现你须要频繁地从客户端代码去查询一个对象的状态,只是为了传递参数、调用该对象的一个方法(好比输出一条日志),那么能够考虑实现一个新的方法,以Lambda或者方法表达式做为参数,新方法在检查完该对象的状态以后才调用原来的方法。调试
若是你发现虽然你的业务代码千差万别,可是它们拥有一样的准备和清理阶段,这时,你彻底能够将这部分代码用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; }
策略模式表明了解决一类算法的通用解决方案,你能够在运行时选择使用哪一种方案。
数字)。你能够从定义一个验证文本(以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");
若是你须要采用某个算法的框架,同时又但愿有必定的灵活度,能对它的某些部分进行改进,那么采用模板方法设计模式是比较通用的方案。
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());
例子:好几家报纸机构,好比《纽约时报》《卫报》以及《世界报》都订阅了新闻,他们但愿当接收的新闻中包含他们感兴趣的关键字时,能获得特别通知。
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); } });
责任链模式是一种建立处理对象序列(好比操做序列)的通用方案。一个处理对象可能须要在完成一些工做以后,将结果传递给另外一个对象,这个对象接着作一些工做,再转交给下一个处理对象,以此类推。一般,这种模式是经过定义一个表明处理对象的抽象类来实现的,在抽象类中会定义一个字段来记录后续对象。一旦对象完成它的工做,处理对象就会将它的工做转交给它的后继。
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?!!");
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); }
文中提到了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。
因为Lambda表达式没有名字,它的栈跟踪可能很难分析,编译器只能为它们指定一个名字,若是你使用了大量的类,其中又包含多个Lambda表达式,这就成了一个很是头痛的问题,这是Java编译器将来版本能够改进的一个方面。
这就是流操做方法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