摘要:通常,這種模式是通過(guò)定義一個(gè)代表處理對(duì)象的抽象類(lèi)來(lái)實(shí)現(xiàn)的,在抽象類(lèi)中會(huì)定義一個(gè)字段來(lái)記錄后續(xù)對(duì)象。工廠模式使用表達(dá)式第章中,我們已經(jīng)知道可以像引用方法一樣引用構(gòu)造函數(shù)。
一、為改善可讀性和靈活性重構(gòu)代碼 1.改善代碼的可讀性
Java 8的新特性也可以幫助提升代碼的可讀性:
使用Java 8,你可以減少冗長(zhǎng)的代碼,讓代碼更易于理解
通過(guò)方法引用和Stream API,你的代碼會(huì)變得更直觀
這里我們會(huì)介紹三種簡(jiǎn)單的重構(gòu),利用Lambda表達(dá)式、方法引用以及Stream改善程序代碼的可讀性:
重構(gòu)代碼,用Lambda表達(dá)式取代匿名類(lèi)
用方法引用重構(gòu)Lambda表達(dá)式
用Stream API重構(gòu)命令式的數(shù)據(jù)處理
2.從匿名類(lèi)到 Lambda 表達(dá)式的轉(zhuǎn)換
在匿名類(lèi)中,this代表的是類(lèi)自身,但是在Lambda中,它代表的是包含類(lèi)。其次,匿名類(lèi)可以屏蔽包含類(lèi)的變量,而Lambda表達(dá)式不
能(它們會(huì)導(dǎo)致編譯錯(cuò)誤),譬如下面這段代碼:
int a = 10; Runnable r1 = () -> { int a = 2; //類(lèi)中已包含變量a System.out.println(a); };
對(duì)于參數(shù)相同的函數(shù)式接口,調(diào)用時(shí)會(huì)造成都符合Lambda表達(dá)式的結(jié)果,不過(guò)NetBeans和IntelliJ都支持這種重構(gòu),它們能自動(dòng)地幫你檢查,避免發(fā)生這些問(wèn)題。
3.從Lambda 表達(dá)式到方法引用的轉(zhuǎn)換按照食物的熱量級(jí)別對(duì)菜肴進(jìn)行分類(lèi):
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表達(dá)式的內(nèi)容抽取到一個(gè)多帶帶的方法中,將其作為參數(shù)傳遞給groupingBy
方法。變換之后,代碼變得更加簡(jiǎn)潔,程序的意圖也更加清晰了:
Map> dishesByCaloricLevel = menu.stream().collect(groupingBy(Dish::getCaloricLevel));
為了實(shí)現(xiàn)這個(gè)方案,你還需要在Dish類(lèi)中添加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.從命令式的數(shù)據(jù)處理切換到 Stream除此之外,我們還應(yīng)該盡量考慮使用靜態(tài)輔助方法,比如comparing、maxBy。
inventory.sort( (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); inventory.sort(comparing(Apple::getWeight));使用Collectors接口可以輕松得到和或者最大值,與采用Lambada表達(dá)式和底層的歸約操作比起來(lái),這種方式要直觀得多.
int totalCalories = menu.stream().map(Dish::getCalories) .reduce(0, (c1, c2) -> c1 + c2); int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
原來(lái):
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)采用函數(shù)接口
用Lambda表達(dá)式帶來(lái)的靈活性,它們分別是:有條件的延遲執(zhí)行和環(huán)繞執(zhí)行。
(2)有條件的延遲執(zhí)行如果你發(fā)現(xiàn)你需要頻繁地從客戶(hù)端代碼去查詢(xún)一個(gè)對(duì)象的狀態(tài),只是為了傳遞參數(shù)、調(diào)用該對(duì)象的一個(gè)方法(比如輸出一條日志),那么可以考慮實(shí)現(xiàn)一個(gè)新的方法,以L(fǎng)ambda或者方法表達(dá)式作為參數(shù),新方法在檢查完該對(duì)象的狀態(tài)之后才調(diào)用原來(lái)的方法。
(3)環(huán)繞執(zhí)行如果你發(fā)現(xiàn)雖然你的業(yè)務(wù)代碼千差萬(wàn)別,但是它們擁有同樣的準(zhǔn)備和清理階段,這時(shí),你完全可以將這部分代碼用Lambda實(shí)現(xiàn)。這種方式的好處是可以重用準(zhǔn)備和清理階段的邏輯,減少重復(fù)冗余的代碼。
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 重構(gòu)面向?qū)ο蟮脑O(shè)計(jì)模式 1.策略模式
策略模式代表了解決一類(lèi)算法的通用解決方案,你可以在運(yùn)行時(shí)選擇使用哪種方案。
數(shù)字)。你可以從定義一個(gè)驗(yàn)證文本(以String的形式表示)的接口入手:
public interface ValidationStrategy { boolean execute(String s); }
其次,你定義了該接口的一個(gè)或多個(gè)具體實(shí)現(xiàn):
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+"); } }
之后,你就可以在你的程序中使用這些略有差異的驗(yàn)證策略了:
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表達(dá)式,則為:
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.模板方法
如果你需要采用某個(gè)算法的框架,同時(shí)又希望有一定的靈活度,能對(duì)它的某些部分進(jìn)行改進(jìn),那么采用模板方法設(shè)計(jì)模式是比較通用的方案。
abstract class OnlineBanking { public void processCustomer(int id){ Customer c = Database.getCustomerWithId(id); makeCustomerHappy(c); } abstract void makeCustomerHappy(Customer c); }
processCustomer方法搭建了在線(xiàn)銀行算法的框架:獲取客戶(hù)提供的ID,然后提供服務(wù)讓用戶(hù)滿(mǎn)意。不同的支行可以通過(guò)繼承OnlineBanking類(lèi),對(duì)該方法提供差異化的實(shí)現(xiàn)。
如果使用Lambda表達(dá)式:
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());
例子:好幾家報(bào)紙機(jī)構(gòu),比如《紐約時(shí)報(bào)》《衛(wèi)報(bào)》以及《世界報(bào)》都訂閱了新聞,他們希望當(dāng)接收的新聞中包含他們感興趣的關(guān)鍵字時(shí),能得到特別通知。
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表達(dá)式后,你無(wú)需顯式地實(shí)例化三個(gè)觀察者對(duì)象,直接傳遞Lambda表達(dá)式表示需要執(zhí)行的行為即可:
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.責(zé)任鏈模式
責(zé)任鏈模式是一種創(chuàng)建處理對(duì)象序列(比如操作序列)的通用方案。一個(gè)處理對(duì)象可能需要在完成一些工作之后,將結(jié)果傳遞給另一個(gè)對(duì)象,這個(gè)對(duì)象接著做一些工作,再轉(zhuǎn)交給下一個(gè)處理對(duì)象,以此類(lèi)推。通常,這種模式是通過(guò)定義一個(gè)代表處理對(duì)象的抽象類(lèi)來(lái)實(shí)現(xiàn)的,在抽象類(lèi)中會(huì)定義一個(gè)字段來(lái)記錄后續(xù)對(duì)象。一旦對(duì)象完成它的工作,處理對(duì)象就會(huì)將它的工作轉(zhuǎn)交給它的后繼。
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);//將兩個(gè)處理對(duì)象鏈接起來(lái) String result = p1.handle("Aren"t labdas really sexy?!!"); System.out.println(result);
使用Lambda表達(dá)式
你可以將處理對(duì)象作為函數(shù)的一個(gè)實(shí)例,或者更確切地說(shuō)作為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表達(dá)式
第3章中,我們已經(jīng)知道可以像引用方法一樣引用構(gòu)造函數(shù)。比如,下面就是一個(gè)引用貸款
(Loan)構(gòu)造函數(shù)的示例:
構(gòu)造器參數(shù)列表要與接口中抽象方法的參數(shù)列表一致!因此,如果構(gòu)造方法中有多個(gè)參數(shù),需要自定義函數(shù)式接口。
SupplierloanSupplier = Loan::new; Loan loan = loanSupplier.get();
通過(guò)這種方式,你可以重構(gòu)之前的代碼,創(chuàng)建一個(gè)Map,將產(chǎn)品名映射到對(duì)應(yīng)的構(gòu)造函數(shù):
final static Map> map = new HashMap<>(); static { map.put("loan", Loan::new); map.put("stock", Stock::new); map.put("bond", Bond::new); }
現(xiàn)在,你可以像之前使用工廠設(shè)計(jì)模式那樣,利用這個(gè)Map來(lái)實(shí)例化不同的產(chǎn)品。
public static Product createProduct(String name){ Supplier三、測(cè)試 Lambda 表達(dá)式p = map.get(name); if(p != null) return p.get(); throw new IllegalArgumentException("No such product " + name); }
你可以借助某個(gè)字段訪(fǎng)問(wèn)Lambda函數(shù)
要對(duì)使用Lambda表達(dá)式的方法進(jìn)行測(cè)試
一種策略是將Lambda表達(dá)式轉(zhuǎn)換為方法引用,然后按照常規(guī)方式
接受函數(shù)作為參數(shù)的方法或者返回一個(gè)函數(shù)的方法(所謂的“高階函數(shù)”,higher-order function,我們?cè)诘?4章會(huì)深入展開(kāi)介紹)更難測(cè)試。如果一個(gè)方法接受Lambda表達(dá)式作為參數(shù),你可以采用的一個(gè)方案是使用不同的Lambda表達(dá)式對(duì)它進(jìn)行測(cè)試。
四、調(diào)試 1.查看棧跟蹤文中提到了List的equals方法
ArrayList、Vector兩者都實(shí)現(xiàn)了List接口、繼承AbstractList抽象類(lèi),其equals方法是在AbstractList類(lèi)中定義的,源代碼如下:public boolean equals(Object o) { if (o == this) return true; // 判斷是否是List列表,只要實(shí)現(xiàn)了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; } // 長(zhǎng)度是否相等 return !(e1.hasNext() || e2.hasNext()); 從源碼可以看出,equals方法并不關(guān)心List的具體實(shí)現(xiàn)類(lèi),只要是實(shí)現(xiàn)了List接口,并且所有元素相等、長(zhǎng)度也相等的話(huà)就表明兩個(gè)List是相等的,所以例子中才會(huì)返回true。
由于Lambda表達(dá)式?jīng)]有名字,它的棧跟蹤可能很難分析,編譯器只能為它們指定一個(gè)名字,如果你使用了大量的類(lèi),其中又包含多個(gè)Lambda表達(dá)式,這就成了一個(gè)非常頭痛的問(wèn)題,這是Java編譯器未來(lái)版本可以改進(jìn)的一個(gè)方面。
2.使用日志調(diào)試這就是流操作方法peek大顯身手的時(shí)候。peek的設(shè)計(jì)初衷就是在流的每個(gè)元素恢復(fù)運(yùn)行之前,插入執(zhí)行一個(gè)動(dòng)作。但是它不像forEach那樣恢復(fù)整個(gè)流的運(yùn)行,而是在一個(gè)元素上完成操作之后,它只會(huì)將操作順承到流水線(xiàn)中的下一個(gè)操作。
Listnumbers = Arrays.asList(2, 3, 4, 5); List result = numbers.stream() .peek(x -> System.out.println("from stream: " + x)) //輸出來(lái)自數(shù)據(jù)源的當(dāng)前元素值 .map(x -> x + 17) .peek(x -> System.out.println("after map: " + x)) //輸 出 map操作的結(jié)果 .filter(x -> x % 2 == 0) .peek(x -> System.out.println("after filter: " + x)) //輸出經(jīng)過(guò)filter操作之后,剩下的元素個(gè)數(shù) .limit(3) .peek(x -> System.out.println("after limit: " + x)) //輸出經(jīng)過(guò)limit操作之后,剩下的元素個(gè)數(shù) .collect(toList());
輸出結(jié)果:
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
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/74298.html
摘要:限制編寫(xiě)并行流,存在一些與非并行流不一樣的約定。底層框架并行流在底層沿用的框架,遞歸式的分解問(wèn)題,然后每段并行執(zhí)行,最終由合并結(jié)果,返回最后的值。 本書(shū)第六章的讀書(shū)筆記,也是我這個(gè)系列的最后一篇讀書(shū)筆記。后面7、8、9章分別講的測(cè)試、調(diào)試與重構(gòu)、設(shè)計(jì)和架構(gòu)的原則以及使用Lambda表達(dá)式編寫(xiě)并發(fā)程序,因?yàn)楣P記不好整理,就不寫(xiě)了,感興趣的同學(xué)自己買(mǎi)書(shū)來(lái)看吧。 并行化流操作 關(guān)于并行與并發(fā)...
摘要:重構(gòu)在不改變代碼的外在的行為的前提下對(duì)代碼進(jìn)行修改最大限度的減少錯(cuò)誤的幾率本質(zhì)上,就是代碼寫(xiě)好之后修改它的設(shè)計(jì)。重構(gòu)可以深入理解代碼并且?guī)椭业健M瑫r(shí)重構(gòu)可以減少引入的機(jī)率,方便日后擴(kuò)展。平行繼承目的在于消除類(lèi)之間的重復(fù)代碼。 重構(gòu) (refactoring) 在不改變代碼的外在的行為的前提下 對(duì)代碼進(jìn)行修改最大限度的減少錯(cuò)誤的幾率 本質(zhì)上, 就是代碼寫(xiě)好之后 修改它的設(shè)計(jì)。 1,書(shū)中...
摘要:但是,最好使用差異化的類(lèi)型定義,函數(shù)簽名如下其實(shí)二者說(shuō)的是同一件事。后者的返回值和初始函數(shù)的返回值相同,即。破壞式更新和函數(shù)式更新的比較三的延遲計(jì)算的設(shè)計(jì)者們?cè)趯⒁霑r(shí)采取了比較特殊的方式。四匹配模式語(yǔ)言中暫時(shí)并未提供這一特性,略。 一、無(wú)處不在的函數(shù) 一等函數(shù):能夠像普通變量一樣使用的函數(shù)稱(chēng)為一等函數(shù)(first-class function)通過(guò)::操作符,你可以創(chuàng)建一個(gè)方法引用,...
摘要:方法接受一個(gè)生產(chǎn)者作為參數(shù),返回一個(gè)對(duì)象,該對(duì)象完成異步執(zhí)行后會(huì)讀取調(diào)用生產(chǎn)者方法的返回值。該方法接收一個(gè)對(duì)象構(gòu)成的數(shù)組,返回由第一個(gè)執(zhí)行完畢的對(duì)象的返回值構(gòu)成的。 一、Future 接口 在Future中觸發(fā)那些潛在耗時(shí)的操作把調(diào)用線(xiàn)程解放出來(lái),讓它能繼續(xù)執(zhí)行其他有價(jià)值的工作,不再需要呆呆等待耗時(shí)的操作完成。打個(gè)比方,你可以把它想象成這樣的場(chǎng)景:你拿了一袋子衣服到你中意的干洗店去洗。...
摘要:正確使用并行流錯(cuò)用并行流而產(chǎn)生錯(cuò)誤的首要原因,就是使用的算法改變了某些共享狀態(tài)。高效使用并行流留意裝箱有些操作本身在并行流上的性能就比順序流差還要考慮流的操作流水線(xiàn)的總計(jì)算成本。 一、并行流 1.將順序流轉(zhuǎn)換為并行流 對(duì)順序流調(diào)用parallel方法: public static long parallelSum(long n) { return Stream.iterate(1L...
摘要:第四章引入流一什么是流流是的新成員,它允許你以聲明性方式處理數(shù)據(jù)集合通過(guò)查詢(xún)語(yǔ)句來(lái)表達(dá),而不是臨時(shí)編寫(xiě)一個(gè)實(shí)現(xiàn)。 第四章 引入流 一、什么是流 流是Java API的新成員,它允許你以聲明性方式處理數(shù)據(jù)集合(通過(guò)查詢(xún)語(yǔ)句來(lái)表達(dá),而不是臨時(shí)編寫(xiě)一個(gè)實(shí)現(xiàn))。就現(xiàn)在來(lái)說(shuō),你可以把它們看成遍歷數(shù)據(jù)集的高級(jí)迭代器。此外,流還可以透明地并行處理,你無(wú)需寫(xiě)任何多線(xiàn)程代碼。 下面兩段代碼都是用來(lái)返回低...
閱讀 2843·2023-04-26 01:02
閱讀 1863·2021-11-17 09:38
閱讀 791·2021-09-22 15:54
閱讀 2899·2021-09-22 15:29
閱讀 888·2021-09-22 10:02
閱讀 3432·2019-08-30 15:54
閱讀 2007·2019-08-30 15:44
閱讀 1586·2019-08-26 13:46