本章的目的是初步理解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之前是不可能作到的!