摘要:通常,這種模式是通過定義一個代表處理對象的抽象類來實現的,在抽象類中會定義一個字段來記錄后續對象。工廠模式使用表達式第章中,我們已經知道可以像引用方法一樣引用構造函數。
一、為改善可讀性和靈活性重構代碼 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> 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> 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; } }
4.從命令式的數據處理切換到 Stream除此之外,我們還應該盡量考慮使用靜態輔助方法,比如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));
原來:
ListdishNames = 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, Consumer3.觀察者模式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 Listobservers = 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{ protected ProcessingObject successor; public void setSuccessor(ProcessingObject 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{ public String handleWork(String text){ return "From Raoul, Mario and Alan: " + text; } } public class SpellCheckerProcessing extends ProcessingObject { public String handleWork(String text){ return text.replaceAll("labda", "lambda"); } }
ProcessingObjectp1 = new HeaderTextProcessing(); ProcessingObject p2 = new SpellCheckerProcessing(); p1.setSuccessor(p2);//將兩個處理對象鏈接起來 String result = p1.handle("Aren"t labdas really sexy?!!"); System.out.println(result);
使用Lambda表達式
你可以將處理對象作為函數的一個實例,或者更確切地說作為UnaryOperator
UnaryOperator5.工廠模式headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text; UnaryOperator spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda"); Function 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)構造函數的示例:
構造器參數列表要與接口中抽象方法的參數列表一致!因此,如果構造方法中有多個參數,需要自定義函數式接口。
SupplierloanSupplier = Loan::new; Loan loan = loanSupplier.get();
通過這種方式,你可以重構之前的代碼,創建一個Map,將產品名映射到對應的構造函數:
final static Map> 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三、測試 Lambda 表達式p = map.get(name); if(p != null) return p.get(); throw new IllegalArgumentException("No such product " + name); }
你可以借助某個字段訪問Lambda函數
要對使用Lambda表達式的方法進行測試
一種策略是將Lambda表達式轉換為方法引用,然后按照常規方式
接受函數作為參數的方法或者返回一個函數的方法(所謂的“高階函數”,higher-order function,我們在第14章會深入展開介紹)更難測試。如果一個方法接受Lambda表達式作為參數,你可以采用的一個方案是使用不同的Lambda表達式對它進行測試。
四、調試 1.查看棧跟蹤文中提到了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所有元素 ListIteratore1 = 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編譯器未來版本可以改進的一個方面。
2.使用日志調試這就是流操作方法peek大顯身手的時候。peek的設計初衷就是在流的每個元素恢復運行之前,插入執行一個動作。但是它不像forEach那樣恢復整個流的運行,而是在一個元素上完成操作之后,它只會將操作順承到流水線中的下一個操作。
Listnumbers = Arrays.asList(2, 3, 4, 5); List 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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74298.html
摘要:限制編寫并行流,存在一些與非并行流不一樣的約定。底層框架并行流在底層沿用的框架,遞歸式的分解問題,然后每段并行執行,最終由合并結果,返回最后的值。 本書第六章的讀書筆記,也是我這個系列的最后一篇讀書筆記。后面7、8、9章分別講的測試、調試與重構、設計和架構的原則以及使用Lambda表達式編寫并發程序,因為筆記不好整理,就不寫了,感興趣的同學自己買書來看吧。 并行化流操作 關于并行與并發...
摘要:重構在不改變代碼的外在的行為的前提下對代碼進行修改最大限度的減少錯誤的幾率本質上,就是代碼寫好之后修改它的設計。重構可以深入理解代碼并且幫助找到。同時重構可以減少引入的機率,方便日后擴展。平行繼承目的在于消除類之間的重復代碼。 重構 (refactoring) 在不改變代碼的外在的行為的前提下 對代碼進行修改最大限度的減少錯誤的幾率 本質上, 就是代碼寫好之后 修改它的設計。 1,書中...
摘要:但是,最好使用差異化的類型定義,函數簽名如下其實二者說的是同一件事。后者的返回值和初始函數的返回值相同,即。破壞式更新和函數式更新的比較三的延遲計算的設計者們在將引入時采取了比較特殊的方式。四匹配模式語言中暫時并未提供這一特性,略。 一、無處不在的函數 一等函數:能夠像普通變量一樣使用的函數稱為一等函數(first-class function)通過::操作符,你可以創建一個方法引用,...
摘要:方法接受一個生產者作為參數,返回一個對象,該對象完成異步執行后會讀取調用生產者方法的返回值。該方法接收一個對象構成的數組,返回由第一個執行完畢的對象的返回值構成的。 一、Future 接口 在Future中觸發那些潛在耗時的操作把調用線程解放出來,讓它能繼續執行其他有價值的工作,不再需要呆呆等待耗時的操作完成。打個比方,你可以把它想象成這樣的場景:你拿了一袋子衣服到你中意的干洗店去洗。...
摘要:正確使用并行流錯用并行流而產生錯誤的首要原因,就是使用的算法改變了某些共享狀態。高效使用并行流留意裝箱有些操作本身在并行流上的性能就比順序流差還要考慮流的操作流水線的總計算成本。 一、并行流 1.將順序流轉換為并行流 對順序流調用parallel方法: public static long parallelSum(long n) { return Stream.iterate(1L...
摘要:第四章引入流一什么是流流是的新成員,它允許你以聲明性方式處理數據集合通過查詢語句來表達,而不是臨時編寫一個實現。 第四章 引入流 一、什么是流 流是Java API的新成員,它允許你以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。就現在來說,你可以把它們看成遍歷數據集的高級迭代器。此外,流還可以透明地并行處理,你無需寫任何多線程代碼。 下面兩段代碼都是用來返回低...
閱讀 2857·2023-04-26 01:02
閱讀 1887·2021-11-17 09:38
閱讀 810·2021-09-22 15:54
閱讀 2913·2021-09-22 15:29
閱讀 905·2021-09-22 10:02
閱讀 3460·2019-08-30 15:54
閱讀 2021·2019-08-30 15:44
閱讀 1608·2019-08-26 13:46