本章的目的是初步理解Lambda表达式能作什么。java
编写可以应对需求变化的代码不容易,经过一个简单例子,并逐步改进这个例子,以展现一些让代码更灵活的最佳作法。就一个农场仓库而言,你必须实现一个从列表中筛选绿苹果的功能。程序员
先把苹果类和苹果列表作好:app
package cn.net.bysoft.chapter2; public class Apple { private Integer id; private String color; private Integer width; public Apple(Integer id, String color, Integer width) { this.id = id; this.color = color; this.width = width; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Integer getWidth() { return width; } public void setWidth(Integer width) { this.width = width; } }
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Apples { private final List<Apple> apples; public Apples() { apples = new ArrayList<Apple>(); apples.add(new Apple(1, "red", 120)); apples.add(new Apple(2, "green", 165)); apples.add(new Apple(3, "red", 175)); apples.add(new Apple(4, "green", 115)); apples.add(new Apple(5, "red", 133)); apples.add(new Apple(6, "green", 129)); apples.add(new Apple(7, "red", 158)); apples.add(new Apple(8, "green", 166)); apples.add(new Apple(9, "red", 117)); apples.add(new Apple(10, "green", 139)); } public List<Apple> getApples() { return apples; } }
第一个解决方案是下面这样的:ide
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Example1 { public static void main(String[] args) { // 过滤出绿色的苹果 // 缺点: // 若是需求改变,想要筛选红苹果,则须要修改源码 // 改进: // 尝试将颜色抽象化 Apples apples = new Apples(); List<Apple> green_apples = filterGreenApples(apples.getApples()); for (Apple apple : green_apples) System.out.println(apple.getId()); } // 过滤出绿色的苹果 public static List<Apple> filterGreenApples(List<Apple> inventory) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if ("green".equals(apple.getColor())) { result.add(apple); } } return result; } /** * output: 2 4 6 8 10 */ }
这是第一个解决方案,这个方案的缺点在于若是需求改变,想要筛选红苹果,则须要修改源码。性能
修改上一个方案,给方法添加一个颜色的参数:优化
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Example2 { public static void main(String[] args) { // 滤出各类颜色的苹果,将颜色抽象成方法参数传递 // 缺点: // 若是需求改变,想要经过重量过滤苹果,就须要在建立一个过滤指定重量的方法 // 可是两个方法中的代码重复的太多 // 改进: // 将过滤颜色的方法与过滤重量的方法结合 Apples apples = new Apples(); List<Apple> green_apples = filterColorApples(apples.getApples(), "red"); for (Apple apple : green_apples) System.out.println(apple.getId()); } // 过滤出指定颜色的苹果,将颜色做为参数传递 public static List<Apple> filterColorApples(List<Apple> inventory, String color) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if (color.equals(apple.getColor())) { result.add(apple); } } return result; } /** * output: 1 3 5 7 9 */ }
这个方案知足了过滤不一样颜色的苹果,可是若是需求在复杂点,须要过滤重量大于150克的苹果。this
这时候咱们可能会想到,在编写一个方法,把重量做为参数传递:spa
// 过滤出指定重量的苹果,将重量做为参数传递 public static List<Apple> filterWidthApples(List<Apple> inventory, int width) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if (apple.getWidth() > width) { result.add(apple); } } return result; }
可是请注意,这个方法中复制(Don't Repeat Yourself)了大量的代码来便利库存,并对每一个苹果应用筛选条件。若是想要改变便利方式来提高性能呢?那就得修改全部方法的实现。.net
如今,能够将颜色和重量结合为一个方法,还须要一个标识来区分筛选哪一个属性。设计
使用一种笨拙的方式来实现:
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Example3 { public static void main(String[] args) { // 过滤出各类颜色或重量的苹果,将颜色和重量都抽象成参数,在定义一个开关参数 // 缺点: // 若是需求改变,想要经过重量过滤苹果,就须要在建立一个过滤指定重量的方法 // 可是两个方法中的代码重复的太多,耦合严重,代码职责不清晰 // 改进: // 使用策略模式解耦 Apples apples = new Apples(); // 找出绿苹果 System.out.println("找出绿苹果"); List<Apple> green_apples = filterApples(apples.getApples(), "green", 0, true); for (Apple apple : green_apples) System.out.println(apple.getId()); // 找出重量大于150的苹果 System.out.println("找出重量大于150的苹果"); List<Apple> heavy_apples = filterApples(apples.getApples(), "", 150, false); for (Apple apple : heavy_apples) System.out.println(apple.getId()); } // 经过指定颜色或者指定重量过滤苹果 public static List<Apple> filterApples(List<Apple> inventory, String color, int width, boolean flag) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if (flag && color.equals(apple.getColor()) || !flag && apple.getWidth() > width) { result.add(apple); } } return result; } /** * output: * 找出绿苹果 2 4 6 8 10 * 找出重量大于150的苹果 2 3 7 8 */ }
你能够这么实现,可是真的很笨拙,这个解决方案再差不过了。首先,客户端代码糟糕透了,true和false是什么意思?此外,这个方案仍是不能很好地应对变化的需求。若是要求你对苹果的不一样属性作筛选,好比大小、形状和产地等,又该怎么办?若是须要组合查询,好比查询绿色的重苹果又该怎么办?
因此咱们须要对行为参数化,使用策略模式来解耦。
首先,定义一个策略接口:
package cn.net.bysoft.chapter2; public interface ApplePredicate { boolean test(Apple apple); }
接着,定义策略类,先定义一个筛选绿苹果的策略,在定义一个筛选重苹果的策略:
package cn.net.bysoft.chapter2; public class AppleGreenColorPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return "green".equals(apple.getColor()); } }
package cn.net.bysoft.chapter2; public class AppleHeavyWeighPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return apple.getWidth() > 150; } }
还能够定义组合筛选的策略:
package cn.net.bysoft.chapter2; public class AppleRedAndHeavyPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return "red".equals(apple) && apple.getWidth() > 150; } }
最后,将ApplePredicate接口做为参数传递到筛选方法:
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Example4 { public static void main(String[] args) { // 使用策略模式过滤苹果,将过滤条件参数化 // 缺点: // 若是有新的过滤策略,好比过滤出红色的苹果,须要在建立一个AppleRedColorPredicate类 // 类太多 Apples apples = new Apples(); // 找出绿苹果 System.out.println("找出绿苹果"); List<Apple> green_apples = filterApples(apples.getApples(), new AppleGreenColorPredicate()); for (Apple apple : green_apples) System.out.println(apple.getId()); // 找出重量大于150的苹果 System.out.println("找出重量大于150的苹果"); List<Apple> heavy_apples = filterApples(apples.getApples(), new AppleHeavyWeighPredicate()); for (Apple apple : heavy_apples) System.out.println(apple.getId()); // 找出重的红苹果 System.out.println("找出重的红苹果"); List<Apple> red_heavy_apples = filterApples(apples.getApples(), new AppleRedAndHeavyPredicate()); for (Apple apple : red_heavy_apples) System.out.println(apple.getId()); } // 经过指定策略来过滤苹果 public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if (p.test(apple)) result.add(apple); } return result; } /** * output: * 找出绿苹果 2 4 6 8 10 * 找出重量大于150的苹果 2 3 7 8 * 找出重的红苹果 3 7 * */ }
如今,经过策略模式,已经把行为抽象出来了,可是这个过程很啰嗦,须要声明不少只要实例化一次的类,并且有新的筛选需求就须要建立一个类。
使用匿名类能够解决这个问题。
匿名类和Java局部类差很少,但匿名类没有名字。它容许你同时声明并实例化一个类:
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Example5 { public static void main(String[] args) { // 使用策略模式过滤苹果,将过滤条件参数化 // 缺点: // 过于啰嗦,每一个匿名类的模板代码太多 Apples apples = new Apples(); // 找出绿苹果 System.out.println("找出绿苹果"); List<Apple> green_apples = filterApples(apples.getApples(), new AppleGreenColorPredicate()); for (Apple apple : green_apples) System.out.println(apple.getId()); // 找出重量大于150的苹果 System.out.println("找出重量大于150的苹果"); List<Apple> heavy_apples = filterApples(apples.getApples(), new AppleHeavyWeighPredicate()); for (Apple apple : heavy_apples) System.out.println(apple.getId()); // 使用匿名类,找出红色的苹果 System.out.println("找出红苹果"); List<Apple> red_apples = filterApples(apples.getApples(), new ApplePredicate() { @Override public boolean test(Apple apple) { return "red".equals(apple.getColor()); } }); for (Apple apple : red_apples) System.out.println(apple.getId()); } // 经过指定策略来过滤苹果 public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if (p.test(apple)) result.add(apple); } return result; } }
就算使用了匿名类解决了类的声明和类的数量过多的问题,可是仍是不够好。
第一,它很笨重,由于它占用了过多的控件。
第二,不少程序员以为它用起来很让人费解。
Java8的设计者引入了Lambda表达式为咱们解决了这个问题。
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Example6 { public static void main(String[] args) { // 使用Lambda表达式过滤苹果 // 缺点: // 目前只能过滤苹果 Apples apples = new Apples(); // 找出绿苹果 System.out.println("找出绿苹果"); List<Apple> green_apples = filterApples(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor())); for (Apple apple : green_apples) System.out.println(apple.getId()); // 找出重量大于150的苹果 System.out.println("找出重量大于150的苹果"); List<Apple> heavy_apples = filterApples(apples.getApples(), (Apple apple) -> apple.getWidth() > 150); for (Apple apple : heavy_apples) System.out.println(apple.getId()); // 找出重的红苹果 System.out.println("找出重的红苹果"); List<Apple> red_apples = filterApples(apples.getApples(), (Apple apple) -> "red".equals(apple.getColor()) && apple.getWidth() > 150); for (Apple apple : red_apples) System.out.println(apple.getId()); } // 经过指定策略来过滤苹果 public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if (p.test(apple)) result.add(apple); } return result; } /** * output: * 找出绿苹果 2 4 6 8 10 * 找出重量大于150的苹果 2 3 7 8 * 找出重的红苹果 3 7 */ }
这段代码干净了不少,可是还能够继续优化,目前只能筛选苹果类,能够将过滤的对象抽象化。
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public interface Predicate<T> { boolean test(T t); public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> result = new ArrayList<>(); for (T e : list) { if (p.test(e)) { result.add(e); } } return result; } }
package cn.net.bysoft.chapter2; import java.util.ArrayList; import java.util.List; public class Example7 { public static void main(String[] args) { // 使用Lambda表达式过滤苹果 // 缺点: // 目前只能过滤苹果 Apples apples = new Apples(); // 找出绿苹果 System.out.println("找出绿苹果"); List<Apple> green_apples = filterApples(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor())); for (Apple apple : green_apples) System.out.println(apple.getId()); // 找出重量大于150的苹果 System.out.println("找出重量大于150的苹果"); List<Apple> heavy_apples = filterApples(apples.getApples(), (Apple apple) -> apple.getWidth() > 150); for (Apple apple : heavy_apples) System.out.println(apple.getId()); // 使用匿名类,找出红色的苹果 System.out.println("找出红苹果"); List<Apple> red_apples = filterApples(apples.getApples(), (Apple apple) -> "red".equals(apple.getColor())); for (Apple apple : red_apples) System.out.println(apple.getId()); } // 经过指定策略来过滤苹果 public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple : inventory) { if (p.test(apple)) result.add(apple); } return result; } }
如今,filter方法能够用在香蕉、桔子、西瓜和芒果等对象上了。
在灵活性和简洁性之间找到了最佳的平衡点,这在Java8之前是不可能作到的!