JAVA編程實戰(zhàn)(第二版)_第1頁
JAVA編程實戰(zhàn)(第二版)_第2頁
JAVA編程實戰(zhàn)(第二版)_第3頁
JAVA編程實戰(zhàn)(第二版)_第4頁
JAVA編程實戰(zhàn)(第二版)_第5頁
已閱讀5頁,還剩444頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

\hJava實戰(zhàn)(第2版)目錄\h第一部分基礎(chǔ)知識\h第1章Java8、9、10以及11的變化\h1.1為什么要關(guān)心Java的變化\h1.2Java怎么還在變\h1.2.1Java在編程語言生態(tài)系統(tǒng)中的位置\h1.2.2流處理\h1.2.3用行為參數(shù)化把代碼傳遞給方法\h1.2.4并行與共享的可變數(shù)據(jù)\h1.2.5Java需要演變\h1.3Java中的函數(shù)\h1.3.1方法和Lambda作為一等值\h1.3.2傳遞代碼:一個例子\h1.3.3從傳遞方法到Lambda\h1.4流\h多線程并非易事\h1.5默認(rèn)方法及Java模塊\h1.6來自函數(shù)式編程的其他好思想\h1.7小結(jié)\h第2章通過行為參數(shù)化傳遞代碼\h2.1應(yīng)對不斷變化的需求\h2.1.1初試牛刀:篩選綠蘋果\h2.1.2再展身手:把顏色作為參數(shù)\h2.1.3第三次嘗試:對你能想到的每個屬性做篩選\h2.2行為參數(shù)化\h第四次嘗試:根據(jù)抽象條件篩選\h2.3對付啰唆\h2.3.1匿名類\h2.3.2第五次嘗試:使用匿名類\h2.3.3第六次嘗試:使用Lambda表達式\h2.3.4第七次嘗試:將List類型抽象化\h2.4真實的例子\h2.4.1用Comparator來排序\h2.4.2用Runnable執(zhí)行代碼塊\h2.4.3通過Callable返回結(jié)果\h2.4.4GUI事件處理\h2.5小結(jié)\h第3章Lambda表達式\h3.1Lambda管中窺豹\h3.2在哪里以及如何使用Lambda\h3.2.1函數(shù)式接口\h3.2.2函數(shù)描述符\h3.3把Lambda付諸實踐:環(huán)繞執(zhí)行模式\h3.3.1第1步:記得行為參數(shù)化\h3.3.2第2步:使用函數(shù)式接口來傳遞行為\h3.3.3第3步:執(zhí)行一個行為\h3.3.4第4步:傳遞Lambda\h3.4使用函數(shù)式接口\h3.4.1Predicate\h3.4.2Consumer\h3.4.3Function\h3.5類型檢查、類型推斷以及限制\h3.5.1類型檢查\h3.5.2同樣的Lambda,不同的函數(shù)式接口\h3.5.3類型推斷\h3.5.4使用局部變量\h3.6方法引用\h3.6.1管中窺豹\h3.6.2構(gòu)造函數(shù)引用\h3.7Lambda和方法引用實戰(zhàn)\h3.7.1第1步:傳遞代碼\h3.7.2第2步:使用匿名類\h3.7.3第3步:使用Lambda表達式\h3.7.4第4步:使用方法引用\h3.8復(fù)合Lambda表達式的有用方法\h3.8.1比較器復(fù)合\h3.8.2謂詞復(fù)合\h3.8.3函數(shù)復(fù)合\h3.9數(shù)學(xué)中的類似思想\h3.9.1積分\h3.9.2與Java8的Lambda聯(lián)系起來\h3.10小結(jié)\h第二部分使用流進行函數(shù)式數(shù)據(jù)處理\h第4章引入流\h4.1流是什么\h4.2流簡介\h4.3流與集合\h4.3.1只能遍歷一次\h4.3.2外部迭代與內(nèi)部迭代\h4.4流操作\h4.4.1中間操作\h4.4.2終端操作\h4.4.3使用流\h4.5路線圖\h4.6小結(jié)\h第5章使用流\h5.1篩選\h5.1.1用謂詞篩選\h5.1.2篩選各異的元素\h5.2流的切片\h5.2.1使用謂詞對流進行切片\h5.2.2截短流\h5.2.3跳過元素\h5.3映射\h5.3.1對流中每一個元素應(yīng)用函數(shù)\h5.3.2流的扁平化\h5.4查找和匹配\h5.4.1檢查謂詞是否至少匹配一個元素\h5.4.2檢查謂詞是否匹配所有元素\h5.4.3查找元素\h5.4.4查找第一個元素\h5.5歸約\h5.5.1元素求和\h5.5.2最大值和最小值\h5.6付諸實踐\h5.6.1領(lǐng)域:交易員和交易\h5.6.2解答\h5.7數(shù)值流\h5.7.1原始類型流特化\h5.7.2數(shù)值范圍\h5.7.3數(shù)值流應(yīng)用:勾股數(shù)\h5.8構(gòu)建流\h5.8.1由值創(chuàng)建流\h5.8.2由可空對象創(chuàng)建流\h5.8.3由數(shù)組創(chuàng)建流\h5.8.4由文件生成流\h5.8.5由函數(shù)生成流:創(chuàng)建無限流\h5.9概述\h5.10小結(jié)\h第6章用流收集數(shù)據(jù)\h6.1收集器簡介\h6.1.1收集器用作高級歸約\h6.1.2預(yù)定義收集器\h6.2歸約和匯總\h6.2.1查找流中的最大值和最小值\h6.2.2匯總\h6.2.3連接字符串\h6.2.4廣義的歸約匯總\h6.3分組\h6.3.1操作分組的元素\h6.3.2多級分組\h6.3.3按子組收集數(shù)據(jù)\h6.4分區(qū)\h6.4.1分區(qū)的優(yōu)勢\h6.4.2將數(shù)字按質(zhì)數(shù)和非質(zhì)數(shù)分區(qū)\h6.5收集器接口\h6.5.1理解Collector接口聲明的方法\h6.5.2全部融合到一起\h6.6開發(fā)你自己的收集器以獲得更好的性能\h6.6.1僅用質(zhì)數(shù)做除數(shù)\h6.6.2比較收集器的性能\h6.7小結(jié)\h第7章并行數(shù)據(jù)處理與性能\h7.1并行流\h7.1.1將順序流轉(zhuǎn)換為并行流\h7.1.2測量流性能\h7.1.3正確使用并行流\h7.1.4高效使用并行流\h7.2分支/合并框架\h7.2.1使用RecursiveTask\h7.2.2使用分支/合并框架的最佳做法\h7.2.3工作竊取\h7.3Spliterator\h7.3.1拆分過程\h7.3.2實現(xiàn)你自己的Spliterator\h7.4小結(jié)\h第三部分使用流和Lambda進行高效編程\h第8章CollectionAPI的增強功能\h8.1集合工廠\h8.1.1List工廠\h8.1.2Set工廠\h8.1.3Map工廠\h8.2使用List和Set\h8.2.1removeIf方法\h8.2.2replaceAll方法\h8.3使用Map\h8.3.1forEach方法\h8.3.2排序\h8.3.3getOrDefault方法\h8.3.4計算模式\h8.3.5刪除模式\h8.3.6替換模式\h8.3.7merge方法\h8.4改進的ConcurrentHashMap\h8.4.1歸約和搜索\h8.4.2計數(shù)\h8.4.3Set視圖\h8.5小結(jié)\h第9章重構(gòu)、測試和調(diào)試\h9.1為改善可讀性和靈活性重構(gòu)代碼\h9.1.1改善代碼的可讀性\h9.1.2從匿名類到Lambda表達式的轉(zhuǎn)換\h9.1.3從Lambda表達式到方法引用的轉(zhuǎn)換\h9.1.4從命令式的數(shù)據(jù)處理切換到Stream\h9.1.5增加代碼的靈活性\h9.2使用Lambda重構(gòu)面向?qū)ο蟮脑O(shè)計模式\h9.2.1策略模式\h9.2.2模板方法\h9.2.3觀察者模式\h9.2.4責(zé)任鏈模式\h9.2.5工廠模式\h9.3測試Lambda表達式\h9.3.1測試可見Lambda函數(shù)的行為\h9.3.2測試使用Lambda的方法的行為\h9.3.3將復(fù)雜的Lambda表達式分為不同的方法\h9.3.4高階函數(shù)的測試\h9.4調(diào)試\h9.4.1查看棧跟蹤\h9.4.2使用日志調(diào)試\h9.5小結(jié)\h第10章基于Lambda的領(lǐng)域特定語言\h10.1領(lǐng)域特定語言\h10.1.1DSL的優(yōu)點和弊端\h10.1.2JVM中已提供的DSL解決方案\h10.2現(xiàn)代JavaAPI中的小型DSL\h10.2.1把StreamAPI當(dāng)成DSL去操作集合\h10.2.2將Collectors作為DSL匯總數(shù)據(jù)\h10.3使用Java創(chuàng)建DSL的模式與技巧\h10.3.1方法鏈接\h10.3.2使用嵌套函數(shù)\h10.3.3使用Lambda表達式的函數(shù)序列\(zhòng)h10.3.4把它們都放到一起\h10.3.5在DSL中使用方法引用\h10.4Java8DSL的實際應(yīng)用\h10.4.1jOOQ\h10.4.2Cucumber\h10.4.3SpringIntegration\h10.5小結(jié)\h第四部分無所不在的Java\h第11章用Optional取代null\h11.1如何為缺失的值建模\h11.1.1采用防御式檢查減少NullPointerException\h11.1.2null帶來的種種問題\h11.1.3其他語言中null的替代品\h11.2Optional類入門\h11.3應(yīng)用Optional的幾種模式\h11.3.1創(chuàng)建Optional對象\h11.3.2使用map從Optional對象中提取和轉(zhuǎn)換值\h11.3.3使用flatMap鏈接Optional對象\h11.3.4操縱由Optional對象構(gòu)成的Stream\h11.3.5默認(rèn)行為及解引用Optional對象\h11.3.6兩個Optional對象的組合\h11.3.7使用filter剔除特定的值\h11.4使用Optional的實戰(zhàn)示例\h11.4.1用Optional封裝可能為null的值\h11.4.2異常與Optional的對比\h11.4.3基礎(chǔ)類型的Optional對象,以及為什么應(yīng)該避免使用它們\h11.4.4把所有內(nèi)容整合起來\h11.5小結(jié)\h第12章新的日期和時間API\h12.1LocalDate、LocalTime、LocalDateTime、Instant、Duration以及Period\h12.1.1使用LocalDate和LocalTime\h12.1.2合并日期和時間\h12.1.3機器的日期和時間格式\h12.1.4定義Duration或Period\h12.2操縱、解析和格式化日期\h12.2.1使用TemporalAdjuster\h12.2.2打印輸出及解析日期–時間對象\h12.3處理不同的時區(qū)和歷法\h12.3.1使用時區(qū)\h12.3.2利用和UTC/格林尼治時間的固定偏差計算時區(qū)\h12.3.3使用別的日歷系統(tǒng)\h12.4小結(jié)\h第13章默認(rèn)方法\h13.1不斷演進的API\h13.1.1初始版本的API\h13.1.2第二版API\h13.2概述默認(rèn)方法\h13.3默認(rèn)方法的使用模式\h13.3.1可選方法\h13.3.2行為的多繼承\(zhòng)h13.4解決沖突的規(guī)則\h13.4.1解決問題的三條規(guī)則\h13.4.2選擇提供了最具體實現(xiàn)的默認(rèn)方法的接口\h13.4.3沖突及如何顯式地消除歧義\h13.4.4菱形繼承問題\h13.5小結(jié)\h第14章Java模塊系統(tǒng)\h14.1模塊化的驅(qū)動力:軟件的推理\h14.1.1關(guān)注點分離\h14.1.2信息隱藏\h14.1.3Java軟件\h14.2為什么要設(shè)計Java模塊系統(tǒng)\h14.2.1模塊化的局限性\h14.2.2單體型的JDK\h14.2.3與OSGi的比較\h14.3Java模塊:全局視圖\h14.4使用Java模塊系統(tǒng)開發(fā)應(yīng)用\h14.4.1從頭開始搭建一個應(yīng)用\h14.4.2細(xì)粒度和粗粒度的模塊化\h14.4.3Java模塊系統(tǒng)基礎(chǔ)\h14.5使用多個模塊\h14.5.1exports子句\h14.5.2requires子句\h14.5.3命名\h14.6編譯及打包\h14.7自動模塊\h14.8模塊聲明及子句\h14.8.1requires\h14.8.2exports\h14.8.3requires的傳遞\h14.8.4exportsto\h14.8.5open和opens\h14.8.6uses和provides\h14.9通過一個更復(fù)雜的例子了解更多\h14.10小結(jié)\h第五部分提升Java的并發(fā)性\h第15章CompletableFuture及反應(yīng)式編程背后的概念\h15.1為支持并發(fā)而不斷演進的Java\h15.1.1線程以及更高層的抽象\h15.1.2執(zhí)行器和線程池\h15.1.3其他的線程抽象:非嵌套方法調(diào)用\h15.1.4你希望線程為你帶來什么\h15.2同步及異步API\h15.2.1Future風(fēng)格的API\h15.2.2反應(yīng)式風(fēng)格的API\h15.2.3有害的睡眠及其他阻塞式操作\h15.2.4實戰(zhàn)驗證\h15.2.5如何使用異步API進行異常處理\h15.3“線框–管道”模型\h15.4為并發(fā)而生的CompletableFuture和結(jié)合器\h15.5“發(fā)布–訂閱”以及反應(yīng)式編程\h15.5.1示例:對兩個流求和\h15.5.2背壓\h15.5.3一種簡單的真實背壓\h15.6反應(yīng)式系統(tǒng)和反應(yīng)式編程\h15.7路線圖\h15.8小結(jié)\h第16章CompletableFuture:組合式異步編程\h16.1Future接口\h16.1.1Future接口的局限性\h16.1.2使用CompletableFuture構(gòu)建異步應(yīng)用\h16.2實現(xiàn)異步API\h16.2.1將同步方法轉(zhuǎn)換為異步方法\h16.2.2錯誤處理\h16.3讓你的代碼免受阻塞之苦\h16.3.1使用并行流對請求進行并行操作\h16.3.2使用CompletableFuture發(fā)起異步請求\h16.3.3尋找更好的方案\h16.3.4使用定制的執(zhí)行器\h16.4對多個異步任務(wù)進行流水線操作\h16.4.1實現(xiàn)折扣服務(wù)\h16.4.2使用Discount服務(wù)\h16.4.3構(gòu)造同步和異步操作\h16.4.4將兩個CompletableFuture對象整合起來,無論它們是否存在依賴\h16.4.5對Future和CompletableFuture的回顧\h16.4.6高效地使用超時機制\h16.5響應(yīng)CompletableFuture的completion事件\h16.5.1對最佳價格查詢器應(yīng)用的優(yōu)化\h16.5.2付諸實踐\h16.6路線圖\h16.7小結(jié)\h第17章反應(yīng)式編程\h17.1反應(yīng)式宣言\h17.1.1應(yīng)用層的反應(yīng)式編程\h17.1.2反應(yīng)式系統(tǒng)\h17.2反應(yīng)式流以及FlowAPI\h17.2.1Flow類\h17.2.2創(chuàng)建你的第一個反應(yīng)式應(yīng)用\h17.2.3使用Processor轉(zhuǎn)換數(shù)據(jù)\h17.2.4為什么Java并未提供FlowAPI的實現(xiàn)\h17.3使用反應(yīng)式庫RxJava\h17.3.1創(chuàng)建和使用Observable\h17.3.2轉(zhuǎn)換及整合多個Observable\h17.4小結(jié)\h第六部分函數(shù)式編程以及Java未來的演進\h第18章函數(shù)式的思考\h18.1實現(xiàn)和維護系統(tǒng)\h18.1.1共享的可變數(shù)據(jù)\h18.1.2聲明式編程\h18.1.3為什么要采用函數(shù)式編程\h18.2什么是函數(shù)式編程\h18.2.1函數(shù)式Java編程\h18.2.2引用透明性\h18.2.3面向?qū)ο蟮木幊毯秃瘮?shù)式編程的對比\h18.2.4函數(shù)式編程實戰(zhàn)\h18.3遞歸和迭代\h18.4小結(jié)\h第19章函數(shù)式編程的技巧\h19.1無處不在的函數(shù)\h19.1.1高階函數(shù)\h19.1.2柯里化\h19.2持久化數(shù)據(jù)結(jié)構(gòu)\h19.2.1破壞式更新和函數(shù)式更新的比較\h19.2.2另一個使用Tree的例子\h19.2.3采用函數(shù)式的方法\h19.3Stream的延遲計算\h19.3.1自定義的Stream\h19.3.2創(chuàng)建你自己的延遲列表\h19.4模式匹配\h19.4.1訪問者模式\h19.4.2用模式匹配力挽狂瀾\h19.5雜項\h19.5.1緩存或記憶表\h19.5.2“返回同樣的對象”意味著什么\h19.5.3結(jié)合器\h19.6小結(jié)\h第20章面向?qū)ο蠛秃瘮?shù)式編程的混合:Java和Scala的比較\h20.1Scala簡介\h20.1.1你好,啤酒\h20.1.2基礎(chǔ)數(shù)據(jù)結(jié)構(gòu):List、Set、Map、Tuple、Stream以及Option\h20.2函數(shù)\h20.2.1Scala中的一等函數(shù)\h20.2.2匿名函數(shù)和閉包\h20.2.3柯里化\h20.3類和trait\h20.3.1更加簡潔的Scala類\h20.3.2Scala的trait與Java8的接口對比\h20.4小結(jié)\h第21章結(jié)論以及Java的未來\h21.1回顧Java8的語言特性\h21.1.1行為參數(shù)化(Lambda以及方法引用)\h21.1.2流\h21.1.3CompletableFuture\h21.1.4Optional\h21.1.5FlowAPI\h21.1.6默認(rèn)方法\h21.2Java9的模塊系統(tǒng)\h21.3Java10的局部變量類型推斷\h21.4Java的未來\h21.4.1聲明處型變\h21.4.2模式匹配\h21.4.3更加豐富的泛型形式\h21.4.4對不變性的更深層支持\h21.4.5值類型\h21.5讓Java發(fā)展得更快\h21.6寫在最后的話\h附錄A其他語言特性的更新\hA.1注解\hA.1.1重復(fù)注解\hA.1.2類型注解\hA.2通用目標(biāo)類型推斷\h附錄B其他類庫的更新\hB.1集合\hB.1.1其他新增的方法\hB.1.2Collections類\hB.1.3Comparator\hB.2并發(fā)\hB.2.1原子操作\hB.2.2ConcurrentHashMap\hB.3Arrays\hB.3.1使用parallelSort\hB.3.2使用setAll和parallelSetAll\hB.3.3使用parallelPrefix\hB.4Number和Math\hB.4.1Number\hB.4.2Math\hB.5Files\hB.6Reflection\hB.7String\h附錄C如何以并發(fā)方式在同一個流上執(zhí)行多種操作\hC.1復(fù)制流\hC.1.1使用ForkingStreamConsumer實現(xiàn)Results接口\hC.1.2開發(fā)ForkingStreamConsumer和BlockingQueueSpliterator\hC.1.3將StreamForker運用于實戰(zhàn)\hC.2性能的考量\h附錄DLambda表達式和JVM字節(jié)碼\hD.1匿名類\hD.2生成字節(jié)碼\hD.3用InvokeDynamic力挽狂瀾\hD.4代碼生成策略注:原文檔電子版(非掃描),需要的請下載本文檔后留言謝謝。第一部分基礎(chǔ)知識第一部分旨在幫助你初步使用Java8。學(xué)完這一部分,你將對Lambda表達式有充分的了解,并可以編寫簡潔而靈活的代碼,能夠輕松適應(yīng)不斷變化的需求。第1章總結(jié)Java的主要變化(Lambda表達式、方法引用、流和默認(rèn)方法),為學(xué)習(xí)后面的內(nèi)容做準(zhǔn)備。第2章介紹行為參數(shù)化,這是Java8非常依賴的一種軟件開發(fā)模式,也是引入Lambda表達式的主要原因。第3章對Lambda表達式和方法引用進行全面的介紹,每一步都提供了代碼示例和測驗。第1章Java8、9、10以及11的變化本章內(nèi)容Java怎么又變了日新月異的計算應(yīng)用背景Java改進的壓力Java8和Java9的核心新特性自1996年JDK1.0(Java1.0)發(fā)布以來,Java已經(jīng)受到了學(xué)生、項目經(jīng)理和程序員等一大批活躍用戶的歡迎。這一語言極具活力,不斷被用在大大小小的項目里。從Java1.1(1997年)到Java7(2011年),Java通過不斷地增加新功能,得到了良好的升級。Java8于2014年3月發(fā)布,Java9于2017年9月發(fā)布,Java10于2018年3月發(fā)布,Java11于2018年9月發(fā)布1。那么,問題來了:為什么要關(guān)心這些變化?1如想了解Oracle公司對JDK的最新支持情況,請訪問\h/technetwork/java/java-se-support-roadmap.html?!g者注1.1為什么要關(guān)心Java的變化我們的理由是,從很多方面來說,Java8所做的改變,其影響比Java歷史上任何一次改變都深遠(Java9新增了效率提升方面的重要改進,但并不傷筋動骨,這些內(nèi)容本章后面會介紹。Java10對類型推斷做了微調(diào))。好消息是,這些改變會讓編程更容易,我們再也不用編寫下面這種啰唆的程序了(按照重量給inventory中的蘋果排序):Collections.sort(inventory,newComparator<Apple>(){publicintcompare(Applea1,Applea2){returna1.getWeight().compareTo(a2.getWeight());}});使用Java8,你能書寫更簡潔的代碼,讓代碼讀起來更接近問題描述本身:inventory.sort(comparing(Apple::getWeight));←----本書第一段Java8代碼這段代碼的意思是“按照重量給庫存蘋果排序”。目前你不用擔(dān)心不理解這段代碼,本書后續(xù)的章節(jié)將會介紹它做了什么,以及如何寫出這樣的代碼。Java8的改變也受到了硬件的影響:平常我們用的CPU都是多核的——你的筆記本電腦或臺式機的處理器可能有四個甚至更多的CPU核。然而,絕大多數(shù)現(xiàn)存的Java程序都只使用了其中一個核,其他三個核都閑著,或者僅消耗了它的一小部分處理能力來運行操作系統(tǒng)或殺毒程序。Java8之前,專家們可能會跟你說,只有通過多線程才能利用多個處理器核。問題是,多線程用起來不僅難,還容易出錯。從Java的演進路徑來看,它一直致力于讓并發(fā)編程更容易、出錯更少。早在1.0版本Java就引入了線程和鎖,甚至還有一個內(nèi)存模型——這是當(dāng)時的最佳做法,然而事實證明,除非你的項目團隊是由專家組成的,否則很難可靠地利用這些基本模型。Java5添加了工業(yè)級的構(gòu)建模塊,如線程池和并發(fā)集合。Java7添加了分支/合并(fork/join)框架,讓并行變得更實用,然而這依舊很困難。Java8提供了一種全新的思想,可以幫助你更容易地實現(xiàn)并行。然而,你仍然需要遵循一些規(guī)則,這些內(nèi)容本書都會逐一介紹。本書還會介紹Java9新增的反應(yīng)式編程支持,它是一種實現(xiàn)并發(fā)的結(jié)構(gòu)化方法。雖然實現(xiàn)反應(yīng)式編程有多種專有的方式,但是RxJava和Akka反應(yīng)式流工具集正日益流行,已成為構(gòu)建高并發(fā)系統(tǒng)的標(biāo)準(zhǔn)方式。基于前文介紹的兩個迫切需求(即編寫更簡潔的代碼,以及更方便地利用處理器的多核)催生出了一座拔地而起相互勾連一致的Java8大廈。先快速了解一下這些想法(希望能引起你的興趣,也希望這些總結(jié)足夠簡潔):StreamAPI;向方法傳遞代碼的技巧;接口的默認(rèn)方法。Java8提供了一個新的API(稱為“流”,Stream),它支持多個數(shù)據(jù)處理的并行操作,其思路和數(shù)據(jù)庫查詢語言類似——從高層的角度描述需求,而由“實現(xiàn)”(這里是Stream庫)來選擇底層最佳執(zhí)行機制。這樣就可以避免用synchronized編寫代碼,這種代碼不僅容易出錯,而且在多核CPU2上執(zhí)行所需的成本也比你想象的要高。2多核CPU的每個處理器核都有獨立的高速緩存。加鎖需要這些高速緩存同步運行,然而這又需要在內(nèi)核間進行較慢的緩存一致性協(xié)議通信。從修正的角度來看,在Java8中加入Stream可以視為添加另外兩項的直接原因:向方法傳遞代碼的簡潔技巧(方法引用、Lambda)和接口中的默認(rèn)方法。如果僅僅把“向方法傳遞代碼”看成引入Stream的結(jié)果,就低估了它在Java8中的應(yīng)用范圍。它提供了一種新的方式,能夠簡潔地表達行為參數(shù)化。比方說,你想要寫兩個只有幾行代碼不同的方法,現(xiàn)在只需把不同的那部分代碼作為參數(shù)傳遞進去就可以了。采用這種編程技巧,代碼更短、更清晰,也比常用的復(fù)制粘貼更少出錯。高手看到這里就會想,Java8之前可以用匿名類實現(xiàn)行為參數(shù)化呀——但是想想本章開頭那個更加簡潔的Java8代碼示例,代碼本身就說明了它有多清晰!Java8里將代碼傳遞給方法的功能(同時也能夠返回代碼并將其包含在數(shù)據(jù)結(jié)構(gòu)中)還讓我們能夠使用一整套新技巧,通常稱為函數(shù)式編程。一言以蔽之,這種被函數(shù)式編程界稱為函數(shù)的代碼,可以被來回傳遞并加以組合,以產(chǎn)生強大的編程語匯。這樣的例子在本書中隨處可見。本章首先從宏觀角度探討語言為什么會演變,然后介紹Java8的核心特性,接著介紹函數(shù)式編程思想——新的特性簡化了使用,而且更適應(yīng)新的計算機體系結(jié)構(gòu)。簡而言之,1.2節(jié)討論Java的演變過程和原因,即Java以前缺乏以簡易方式利用多核并行的能力。1.3節(jié)介紹為什么把代碼傳遞給方法在Java8里是如此強大的一個新的編程語匯。1.4節(jié)對Stream做同樣的介紹:Stream是Java8表示有序數(shù)據(jù)以及這些數(shù)據(jù)是否可以并行處理的新方式。1.5節(jié)解釋如何利用Java8中的默認(rèn)方法功能讓接口和庫的演變更順暢、編譯更少,還會介紹Java9中新增的模塊,有了這一特性,Java系統(tǒng)組件就不會再被稱為“只是包的JAR文件”了。最后,1.6節(jié)展望在Java和其他共用JVM的語言中進行函數(shù)式編程的思想??偟膩碚f,本章會介紹整體脈絡(luò),而細(xì)節(jié)會在本書的其余部分中逐一展開。請盡情享受吧!1.2Java怎么還在變20世紀(jì)60年代,人們開始追求完美的編程語言。當(dāng)時著名的計算機科學(xué)家PeterLandin在1966年的一篇標(biāo)志性論文3中提到那時已經(jīng)有700種編程語言了,并推測了接下來的700種會是什么樣子,文中也對類似于Java8中的函數(shù)式編程進行了討論。3P.J.Landin,“TheNext700ProgrammingLanguages,”CACM9(3):157–65,March1966。之后,又出現(xiàn)了數(shù)以千計的編程語言。于是學(xué)者們得出結(jié)論:編程語言就像生態(tài)系統(tǒng)一樣,新的語言會出現(xiàn),舊語言則被取代,除非它們不斷演變。我們都希望出現(xiàn)一種完美的通用語言,可在現(xiàn)實中,某些語言只是更適合某些方面。比如,C和C++仍然是構(gòu)建操作系統(tǒng)和各種嵌入式系統(tǒng)的流行工具,因為它們編寫出的程序盡管安全性不佳,但運行時占用資源少。缺乏安全性可能會導(dǎo)致程序意外崩潰,并把安全漏洞暴露給病毒等。確實,Java和C#等安全型語言在諸多運行資源不太緊張的應(yīng)用中已經(jīng)取代了C和C++。先搶占市場通常能夠嚇退競爭對手。為了一個功能而改用新的語言和工具鏈往往太痛苦了,但新來者最終會取代現(xiàn)有的語言,除非后者演變得夠快,能跟上節(jié)奏。年紀(jì)大一點的讀者大多可以列舉出一堆這樣的語言——他們以前用過,但是現(xiàn)在這些語言已經(jīng)不流行了。隨便列舉幾個吧:Ada、Algol、COBOL、Pascal、Delphi、SNOBOL等。你是一位Java程序員。在過去近20年的時間里,Java已經(jīng)成功地霸占了編程生態(tài)系統(tǒng)中的一大塊,同時替代了競爭對手語言。下面來看看其中的原因。1.2.1Java在編程語言生態(tài)系統(tǒng)中的位置Java天資不錯。從一開始,它就是一門精心設(shè)計的面向?qū)ο蟮恼Z言,提供了大量有用的庫。由于有集成的線程和鎖的支持,它從第一天起就支持小規(guī)模并發(fā)(并且它很有先見之明地承認(rèn),在硬件無關(guān)的內(nèi)存模型中,并發(fā)線程在多核處理器上發(fā)生意外的概率比單核處理器上大得多)。此外,將Java編譯成JVM字節(jié)碼(一種很快就被每一種瀏覽器支持的虛擬機代碼)意味著它成為了互聯(lián)網(wǎng)applet(小應(yīng)用)的首選。(你還記得applet嗎?)確實,Java虛擬機(JVM)及其字節(jié)碼可能會變得比Java語言本身更重要,而且對于某些應(yīng)用來說,Java可能會被同樣運行在JVM上的競爭對手語言(如Scala或Groovy)取代。JVM各種最新的更新(例如JDK7中的新invokedynamic字節(jié)碼)旨在幫助這些競爭對手語言在JVM上順利運行,并與Java交互操作。Java也已經(jīng)成功地占領(lǐng)了嵌入式計算的若干領(lǐng)域,從智能卡、烤面包機、機頂盒到汽車制動系統(tǒng)。Java是如何進入通用編程市場的?面向?qū)ο笤?0世紀(jì)90年代開始流行,原因有兩個:封裝原則使得其軟件工程問題比C少;作為一個思維模型,它輕松地反映了Windows95及之后的WIMP編程模式??梢赃@樣總結(jié):一切都是對象,單擊鼠標(biāo)就能給處理程序發(fā)送一個事件消息(在Mouse對象中觸發(fā)clicked方法)。Java的“一次編寫,隨處運行”模式,以及早期瀏覽器安全地執(zhí)行Java小應(yīng)用的能力讓它占領(lǐng)了大學(xué)市場,畢業(yè)生隨后又把它帶進了業(yè)界。開始時由于運行成本比C/C++要高,Java還遇到了一些阻力,但后來機器變得越來越快,程序員的時間也變得越來越重要了。微軟的C#進一步驗證了Java的面向?qū)ο竽P?。但是,編程語言生態(tài)系統(tǒng)的氣候正在變化。程序員越來越多地要處理所謂的大數(shù)據(jù)(數(shù)百萬兆甚至更多字節(jié)的數(shù)據(jù)集),并希望利用多核計算機或計算集群來有效地處理。這意味著需要使用并行處理——Java以前對此并不支持。你可能接觸過其他編程領(lǐng)域的思想,比如Google的map-reduce,或使用過相對容易的數(shù)據(jù)庫查詢語言(如SQL)執(zhí)行數(shù)據(jù)操作,它們能幫助你處理大量數(shù)據(jù)和多核CPU。圖1-1總結(jié)了語言生態(tài)系統(tǒng):把這幅圖看作編程問題空間,每個地方生長的主要植物就是程序最喜歡的語言。氣候變化的意思是,新的硬件或新的編程因素(例如,“我為什么不能用SQL的風(fēng)格來寫程序?”)意味著新項目優(yōu)選的語言各有不同,就像地區(qū)氣溫上升就意味著葡萄在較高的緯度也能長得好。當(dāng)然這會有滯后——很多老農(nóng)會一直種植著傳統(tǒng)作物??傊?,新的語言不斷出現(xiàn),并因為迅速適應(yīng)了氣候變化,越來越受歡迎。圖1-1編程語言生態(tài)系統(tǒng)和氣候變化對程序員來說,Java8的主要好處在于它提供了更多的編程工具和概念,能以更快、更簡潔、更易于維護的方式解決新的或現(xiàn)有的編程問題,其中簡潔和易維護更重要。雖然這些概念對于Java來說是新的,但是研究型的語言已經(jīng)證明了它們的強大。我們會重點探討三個編程概念背后的思想,它們促使Java8開發(fā)出了利用并行和編寫更簡潔代碼的功能。這里介紹它們的順序和本書其余部分略有不同,一方面是為了類比Unix,另一方面是為了揭示Java8新的多核并行中存在的“因為這個所以需要那個”的依賴關(guān)系。另一個影響Java氣候變化的因素影響Java氣候變化的另一個因素是大型系統(tǒng)的設(shè)計方式?,F(xiàn)在,越來越多的大型系統(tǒng)會集成來自第三方的大型子系統(tǒng),而這些子系統(tǒng)可能又構(gòu)建于別的供應(yīng)商提供的組件之上。更糟糕的是,這些組件以及它們的接口也會不斷演進。為了解決這些設(shè)計風(fēng)格上的問題,Java8和Java9提供了默認(rèn)方法和模塊系統(tǒng)。接下來的三個小節(jié)會逐一介紹驅(qū)動Java8設(shè)計的三個編程概念。1.2.2流處理第一個編程概念是流處理。流是一系列數(shù)據(jù)項,一次只生成一項。程序可以從輸入流中一個一個讀取數(shù)據(jù)項,然后以同樣的方式將數(shù)據(jù)項寫入輸出流。一個程序的輸出流很可能是另一個程序的輸入流。一個實際的例子是在Unix或Linux中,很多程序都從標(biāo)準(zhǔn)輸入(Unix和C中的stdin,Java中的System.in)讀取數(shù)據(jù),然后把結(jié)果寫入標(biāo)準(zhǔn)輸出(Unix和C中的stdout,Java中的System.out)。首先來看一點點背景:Unix的cat命令會把兩個文件連接起來創(chuàng)建一個流,tr會轉(zhuǎn)換流中的字符,sort會對流中的行進行排序,tail-3則給出流的最后三行。Unix命令行允許這些程序通過管道(|)連接在一起,比如下面這段代碼會假設(shè)file1和file2中每行都只有一個單詞,先把字母轉(zhuǎn)換成小寫字母,然后打印出按照詞典順序排在最后的三個單詞:catfile1file2|tr"[A-Z]""[a-z]"|sort|tail-3我們說sort把一個行流4作為輸入,產(chǎn)生了另一個行流(進行排序)作為輸出,如圖1-2所示。請注意在Unix中,這些命令(cat、tr、sort和tail)是同時執(zhí)行的,這樣sort就可以在cat或tr完成前先處理頭幾行。就像汽車組裝流水線一樣,汽車排隊進入加工站,每個加工站會接收、修改汽車,然后將之傳遞給下一站做進一步的處理。盡管流水線實際上是一個序列,但不同加工站的運行一般是并行的。4有語言潔癖的人會說“字符流”,不過認(rèn)為sort會對行重新排序比較簡單。圖1-2操作流的Unix命令基于這一思想,Java8在java.util.stream中添加了一個StreamAPI。Stream<T>就是一系列T類型的項目。你現(xiàn)在可以把它看成一種比較花哨的迭代器。StreamAPI的很多方法可以鏈接起來形成一個復(fù)雜的流水線,就像先前例子里面鏈接起來的Unix命令一樣。推動這種做法的關(guān)鍵在于,現(xiàn)在你可以在一個更高的抽象層次上寫Java8程序了:思路變成了把這樣的流變成那樣的流(就像寫數(shù)據(jù)庫查詢語句時的那種思路),而不是一次只處理一個項目。另一個好處是,Java8可以透明地把輸入的不相關(guān)部分拿到幾個CPU核上去分別執(zhí)行你的Stream操作流水線——這是幾乎免費的并行,用不著去費勁搞Thread了。本書第4~7章會仔細(xì)討論Java8的StreamAPI。1.2.3用行為參數(shù)化把代碼傳遞給方法Java8中增加的另一個編程概念是通過API來傳遞代碼的能力。這聽起來實在太抽象了。在Unix的例子里,你可能想告訴sort命令使用自定義排序。雖然sort命令支持通過命令行參數(shù)來執(zhí)行各種預(yù)定義類型的排序,比如倒序,但這畢竟是有限的。比方說,你有一堆發(fā)票代碼,格式類似于2013UK0001、2014US0002……其中前四位數(shù)代表年份,接下來兩個字母代表國家,最后四位是客戶的代碼。你可能想按照年份、客戶代碼,甚至國家來對發(fā)票進行排序。你真正想要的是,能夠給sort命令一個參數(shù)讓用戶定義順序:給sort命令傳遞一段獨立的代碼。那么,直接套在Java上,你是要讓sort方法利用自定義的順序進行比較。你可以寫一個compareUsingCustomerId來比較兩張發(fā)票的代碼,但是在Java8之前,你無法把這個方法傳給另一個方法。你可以像本章開頭介紹的那樣,創(chuàng)建一個Comparator對象,將之傳遞給sort方法,不過這不但啰唆,而且讓“重用現(xiàn)有行為”的思想變得不那么清楚了。Java8增加了把方法(你的代碼)作為參數(shù)傳遞給另一個方法的能力。圖1-3是基于圖1-2畫出的,它描繪了這種思路。我們把這一概念稱為行為參數(shù)化。它的重要之處在哪兒呢?StreamAPI就是構(gòu)建在通過傳遞代碼使操作行為實現(xiàn)參數(shù)化的思想上的,當(dāng)把compareUsingCustomerId傳進去,你就把sort的行為參數(shù)化了。圖1-3將compareUsingCustomerId方法作為參數(shù)傳給sort我們將在1.3節(jié)中概述這種方式,第2章和第3章再進行詳細(xì)討論。第18章和第19章將討論這一功能的高級用法,還有函數(shù)式編程自身的一些技巧。1.2.4并行與共享的可變數(shù)據(jù)第三個編程概念更隱晦一點,它源自前面討論流處理能力時說的“幾乎免費的并行”。你需要放棄什么嗎?你可能需要稍微改變一下編寫傳給流方法的行為的方法。這些改變一開始可能會讓你有點兒不舒服,但一旦習(xí)慣了你就會愛上它們。你提供的行為必須能夠同時在不同的輸入上安全地執(zhí)行。一般情況下這就意味著,所寫的代碼不能訪問共享的可變數(shù)據(jù)來完成它的工作。這些函數(shù)有時被稱為“純函數(shù)”“無副作用函數(shù)”或“無狀態(tài)函數(shù)”,第18章和第19章會詳細(xì)討論。前面說的并行只有在你的代碼的多個副本可以獨立工作時才能進行。但如果要寫入的是一個共享變量或?qū)ο?,就行不通了:如果兩個進程需要同時修改這個共享變量怎么辦?(1.4節(jié)通過配圖給出了更詳細(xì)的解釋。)在后續(xù)章節(jié)中,你會進一步了解這種風(fēng)格。Java8的流實現(xiàn)并行比Java現(xiàn)有的ThreadAPI更容易,因此,盡管可以使用synchronized來打破“不能有共享的可變數(shù)據(jù)”這一規(guī)則,但這相當(dāng)于是在和整個體系作對,因為它使所有圍繞這一規(guī)則做出的優(yōu)化都失去意義了。在多個處理器核之間使用synchronized,其代價往往比你預(yù)期的要大得多,因為同步迫使代碼按照順序執(zhí)行,而這與并行處理的宗旨相悖。沒有共享的可變數(shù)據(jù),以及將方法和函數(shù)(即代碼)傳遞給其他方法的能力,這兩個要點是函數(shù)式編程范式的基石,第18章和第19章會詳細(xì)討論。與此相反,在命令式編程范式中,你寫的程序則是一系列改變狀態(tài)的指令。“不能有共享的可變數(shù)據(jù)”意味著,一個方法可以通過它將參數(shù)值轉(zhuǎn)換為結(jié)果的方式來完整描述,換句話說,它的行為就像一個數(shù)學(xué)函數(shù),沒有可見的副作用。1.2.5Java需要演變前面已經(jīng)介紹了Java的演變。例如,引入泛型,以及使用List<String>而不只是List,一開始可能都挺煩人的,但現(xiàn)在你已經(jīng)熟悉了這種風(fēng)格和它所帶來的好處,即在編譯時能發(fā)現(xiàn)更多錯誤,且代碼更易讀,因為你現(xiàn)在知道列表里面是什么了。其他改變使得表達普通的東西變得更容易,例如,使用for-each循環(huán),而不用暴露Iterator里面的模板寫法。Java8中的主要變化反映了它開始遠離常側(cè)重改變現(xiàn)有值的經(jīng)典面向?qū)ο笏枷?,而向函?shù)式編程領(lǐng)域轉(zhuǎn)變。在函數(shù)式編程中,在大體上考慮想做什么(例如,創(chuàng)建一個值來代表所有從A到B的低于給定價格的路線)被視為頭等大事,并和具體實現(xiàn)方式(例如,掃描一個數(shù)據(jù)結(jié)構(gòu)并修改某些元素)區(qū)分開來。請注意,如果極端點兒來說,傳統(tǒng)的面向?qū)ο缶幊毯秃瘮?shù)式編程可能看起來是沖突的。但是我們的理念是獲取兩種編程范式中的精華,以便為任務(wù)找到理想的工具。1.3節(jié)和1.4節(jié)會詳細(xì)討論。簡而言之,語言需要不斷改進,以適應(yīng)硬件的更新或滿足程序員的期待(如果你還不夠信服,想想COBOL可一度是最重要的商用語言之一呢)。要堅持下去,Java必須通過增加新功能來改進,而且只有新功能被人使用,變化才有意義。所以,使用Java8,你就是在保護你作為Java程序員的職業(yè)生涯。除此之外,我們有一種感覺——你一定會喜歡Java8的新功能。隨便問問哪個用過Java8的人,看看他們愿不愿意退回去使用舊版本。還有,用生態(tài)系統(tǒng)打比方的話,Java8的新功能使得Java能夠征服如今被其他語言占領(lǐng)的編程任務(wù)領(lǐng)地,所以對Java8程序員的需求更多了。下面將逐一介紹Java8中的新概念,并順便指出哪一章還會詳細(xì)討論這些概念。1.3Java中的函數(shù)編程語言中的函數(shù)一詞通常是指方法,尤其是靜態(tài)方法,這是在數(shù)學(xué)函數(shù),也就是沒有副作用的函數(shù)之外的一個新含義。幸運的是,你將會看到,當(dāng)Java8提到函數(shù)時,這兩種用法幾乎是一致的。Java8中新增了函數(shù),作為值的一種新形式。它有助于使用1.4節(jié)中談到的流,有了它,Java8可以在多核處理器上進行并行編程。首先來展示一下作為值的函數(shù)本身的有用之處。想想Java程序可能操作的值吧。首先有原始值,比如42(int類型)和3.14(double類型)。其次,值可以是對象(更嚴(yán)格地說是對象的引用)。獲得對象的唯一途徑是利用new,這也許是通過工廠方法或庫函數(shù)實現(xiàn)的;對象引用指向一個類的實例。例子包括"abc"(String類型)、newInteger(1111)(Integer類型),以及newHashMap<Integer,String>(100)的結(jié)果——它顯式調(diào)用了HashMap的構(gòu)造函數(shù)。甚至數(shù)組也是對象。那么有什么問題呢?為了幫助回答這個問題,我們要注意到,編程語言的整個目的就在于操作值,按照歷史上編程語言的傳統(tǒng),這些值應(yīng)被稱為一等值(或一等公民)。編程語言中的其他結(jié)構(gòu)也許有助于表示值的結(jié)構(gòu),但在程序執(zhí)行期間不能傳遞,因而是二等值。前面所說的值是Java中的一等值,但其他很多Java概念(比如方法和類等)則是二等值。用方法來定義類很不錯,類還可以實例化來產(chǎn)生值,但方法和類本身都不是值。這又有什么關(guān)系呢?還真有,人們發(fā)現(xiàn),在運行時傳遞方法能將方法變成一等值。這在編程中非常有用,因此Java8的設(shè)計者把這個功能加入到了Java中。順便說一下,你可能會想,讓類等其他二等值也變成一等值可能也是個好主意。有很多語言,比如Smalltalk和JavaScript,都探索過這條路。1.3.1方法和Lambda作為一等值Scala和Groovy等語言的實踐已經(jīng)證明,讓方法等概念作為一等值可以擴充程序員的工具庫,從而讓編程變得更容易。一旦程序員熟悉了這個強大的功能,就再也不愿意使用沒有這一功能的語言了。因此,Java8的設(shè)計者決定允許將方法作為值,讓編程更輕松。此外,讓方法作為值也構(gòu)成了其他幾個Java8功能(比如Stream)的基礎(chǔ)。我們介紹的Java8的第一個新功能是方法引用。比方說,你想要篩選一個目錄中的所有隱藏文件。你需要編寫一個方法,然后給它一個File,它就會告訴你文件是不是隱藏的。幸好,F(xiàn)ile類里面有一個叫作isHidden的方法。可以把它看作一個函數(shù),接受一個File,返回一個布爾值。但要用它做篩選,需要把它包在一個FileFilter對象里,然后傳遞給File.listFiles方法,如下所示:File[]hiddenFiles=newFile(".").listFiles(newFileFilter(){publicbooleanaccept(Filefile){returnfile.isHidden();←----篩選隱藏文件}});呃,真可怕!雖然只有三行,但這三行可真夠繞的。我們第一次碰到的時候肯定都說過:“非得這樣不可嗎?”已經(jīng)有一個方法isHidden可用,為什么非得把它包在一個啰唆的FileFilter類里面再實例化呢?因為在Java8之前你必須這么做!如今在Java8里,你可以把代碼重寫成這樣:File[]hiddenFiles=newFile(".").listFiles(File::isHidden);哇!酷不酷?你已經(jīng)有了函數(shù)isHidden,因此只需用Java8的方法引用::語法(即“把這個方法作為值”)將其傳給listFiles方法。請注意,我們也開始用函數(shù)代表方法了。稍后會解釋這個機制是如何工作的。一個好處是,你的代碼現(xiàn)在讀起來更接近問題的陳述了。方法不再是二等值了。與用對象引用傳遞對象類似(對象引用是用new創(chuàng)建的),在Java8里寫下File::isHidden的時候,你就創(chuàng)建了一個方法引用,你同樣可以傳遞它。第3章會詳細(xì)討論這一概念。只要方法中有代碼(方法中的可執(zhí)行部分),那么用方法引用就可以傳遞代碼,如圖1-3所示。圖1-4說明了這一概念。你在下一節(jié)中還將看到一個具體的例子——從庫存中選擇蘋果。圖1-4將方法引用File::isHidden傳遞給listFiles方法Lambda——匿名函數(shù)除了允許(命名)函數(shù)成為一等值外,Java8還體現(xiàn)了更廣義的將函數(shù)作為值的思想,包括Lambda5(或匿名函數(shù))。比如,你現(xiàn)在可以寫(intx)->x+1,表示“調(diào)用時給定參數(shù),就返回值的函數(shù)”。你可能會想這有什么必要呢?因為你可以在MyMathsUtils類里面定義一個add1方法,然后寫MyMathsUtils::add1嘛!確實是可以,但要是你沒有方便的方法和類可用,新的Lambda語法更簡潔。第3章會詳細(xì)討論Lambda。我們說使用這些概念的程序具有函數(shù)式編程風(fēng)格,這句話的意思是“編寫把函數(shù)作為一等值來傳遞的程序”。5最初是根據(jù)希臘字母λ命名的。雖然Java中不使用這個符號,但是名稱還是被保留了下來。1.3.2傳遞代碼:一個例子來看一個例子,看看它是如何幫助你寫程序的,我們在第2章還會進行更詳細(xì)的討論。所有的示例代碼均可見于圖靈社區(qū)本書主頁\h/book/2659“隨書下載”處。假設(shè)你有一個Apple類,它有一個getColor方法,還有一個變量inventory保存著一個Apples列表。你可能想要選出所有的綠蘋果(此處使用包含值GREEN和RED的Color枚舉類型),并返回一個列表。通常用篩選(filter)一詞來表達這個概念。在Java8之前,你可能會寫這樣一個方法filterGreenApples:publicstaticList<Apple>filterGreenApples(List<Apple>inventory){List<Apple>result=newArrayList<>();←----result是用來累積結(jié)果的List,開始為空,然后一個個加入綠蘋果for(Appleapple:inventory){if(GREEN.equals(apple.getColor())){←----加粗顯示的代碼會僅僅選出綠蘋果result.add(apple);}}returnresult;}但是接下來,有人可能想要選出重的蘋果,比如超過150克的蘋果,于是你心情沉重地寫了下面這個方法,甚至用了復(fù)制粘貼:publicstaticList<Apple>filterHeavyApples(List<Apple>inventory){List<Apple>result=newArrayList<>();for(Appleapple:inventory){if(apple.getWeight()>150){←----這里加粗顯示的代碼會僅僅選出重的蘋果result.add(apple);}}returnresult;}我們都知道軟件工程中復(fù)制粘貼的危險——給一個做了更新和修正,卻忘了另一個。嘿,這兩個方法只有一行不同:if里面加粗的那行條件。如果這兩個加粗的方法之間的差異僅僅是接受的重量范圍不同,那么你只要把接受的重量上下限作為參數(shù)傳遞給filter就行了,比如指定(150,1000)來選出重的蘋果(超過150克),或者指定(0,80)來選出輕的蘋果(低于80克)。但是,前面提過了,Java8會把條件代碼作為參數(shù)傳遞進去,這樣可以避免filter方法中出現(xiàn)重復(fù)的代碼。現(xiàn)在你可以寫:publicstaticbooleanisGreenApple(Appleapple){returnGREEN.equals(apple.getColor());}publicstaticbooleanisHeavyApple(Appleapple){returnapple.getWeight()>150;}publicinterfacePredicate<T>{←----寫出來是為了清晰(平常只要從java.util.function導(dǎo)入就可以了)booleantest(Tt);}staticList<Apple>filterApples(List<Apple>inventory,Predicate<Apple>p){←----方法作為Predicate參數(shù)p傳遞進去(見附注欄“什么是謂詞?”)List<Apple>result=newArrayList<>();for(Appleapple:inventory){if(p.test(apple)){←----蘋果符合p所代表的條件嗎result.add(apple);}}returnresult;}要用它的話,你可以寫:filterApples(inventory,Apple::isGreenApple);或者filterApples(inventory,Apple::isHeavyApple);接下來的兩章會詳細(xì)討論它是怎么工作的?,F(xiàn)在重要的是你可以在Java8里面?zhèn)鬟f方法了!什么是謂詞?前面的代碼傳遞了方法Apple::isGreenApple(它接受參數(shù)Apple并返回一個boolean)給filterApples,后者則希望接受一個Predicate<Apple>參數(shù)。謂詞(predicate)在數(shù)學(xué)上常常用來代表類似于函數(shù)的東西,它接受一個參數(shù)值,并返回true或false。后面你會看到,Java8也允許你寫Function<Apple,Boolean>——在學(xué)校學(xué)過函數(shù)卻沒學(xué)過謂詞的讀者對此可能更熟悉,但用Predicate<Apple>是更標(biāo)準(zhǔn)的方式,效率也會更高一點兒,這避免了把boolean封裝在Boolean里面。1.3.3從傳遞方法到Lambda把方法作為值來傳遞顯然很有用,但要是為類似于isHeavyApple和isGreenApple這種可能只用一兩次的短方法寫一堆定義就有點兒煩人了。不過Java8也解決了這個問題,它引入了一套新記法(匿名函數(shù)或Lambda),讓你可以寫filterApples(inventory,(Applea)->GREEN.equals(a.getColor()));或者filterApples(inventory,(Applea)->a.getWeight()>150);甚至filterApples(inventory,(Applea)->a.getWeight()<80||RED.equals(a.getColor()));所以,你甚至不需要為只用一次的方法寫定義。代碼更干凈、更清晰,因為你用不著去找自己到底傳遞了什么代碼。但要是Lambda的長度多于幾行(它的行為也不是一目了然)的話,那你還是應(yīng)該用方法引用來指向一個有描述性名稱的方法,而不是使用匿名的Lambda。你應(yīng)該以代碼的清晰度為準(zhǔn)繩。Java8的設(shè)計師幾乎可以就此打住了,要不是有了多核CPU,可能他們真的就到此為止了。函數(shù)式編程竟然如此強大,后面你會有更深的體會。本來,Java加上filter和幾個相關(guān)的東西作為通用庫方法就足以讓人滿意了,比如static<T>Collection<T>filter(Collection<T>c,Predicate<T>p);這樣你甚至不需要寫filterApples了,因為比如先前的調(diào)用filterApples(inventory,(Applea)->a.getWeight()>150);就可以直接調(diào)用庫方法filter:filter(inventory,(Applea)->a.getWeight()>150);但是,為了更好地利用并行,Java的設(shè)計師沒有這么做。Java8中有一整套新的類CollectionAPI——Stream,它有一套類似于函數(shù)式程序員熟悉的filter的操作,比如map、reduce,還有接下來要討論的在Collection和Stream之間做轉(zhuǎn)換的方法。1.4流幾乎每個Java應(yīng)用都會制造和處理集合。但集合用起來并不總是那么理想。比方說,你需要從一個列表中篩選金額較高的交易,然后按貨幣分組。你需要寫一大堆模板代碼來實現(xiàn)這個數(shù)據(jù)處理命令,如下所示:Map<Currency,List<Transaction>>transactionsByCurrencies=newHashMap<>();←----建立累積交易分組的Mapfor(Transactiontransaction:transactions){←----遍歷交易的Listif(transaction.getPrice()>1000){←----篩選金額較高的交易Currencycurrency=transaction.getCurrency();←----提取交易貨幣List<Transaction>transactionsForCurrency=transactionsByCurrencies.get(currency);if(transactionsForCurrency==null){←----如果這個貨幣的分組Map是空的,那就建立一個transactionsForCurrency=newArrayList<>();transactionsByCurrencies.put(currency,transactionsForCurrency);}transactionsForCurrency.add(transaction);←----將當(dāng)前遍歷的交易添加到具有同一貨幣的交易List中}}此外,很難一眼看出這些代碼是做什么的,因為有好幾個嵌套的控制流指令。有了StreamAPI,你現(xiàn)在可以這樣解決這個問題了:importstaticjava.util.stream.Collectors.groupingBy;Map<Currency,List<Transaction>>transactionsByCurrencies=transactions.stream().filter((Transactiont)->t.getPrice()>1000)←----篩選金額較高的交易.collect(groupingBy(Transaction::getCurrency));←----按貨幣分組這看起來有點兒神奇,不過現(xiàn)在先不用擔(dān)心。第4~7章會專門講述怎么理解StreamAPI。現(xiàn)在值得注意的是,StreamAPI處理數(shù)據(jù)的方式與CollectionAPI不同。用集合的話,你得自己管理迭代過程。你得用for-each循環(huán)一個個地迭代元素,然后再處理元素。我們把這種數(shù)據(jù)迭代方法稱為外部迭代。相反,有了StreamAPI,你根本用不著操心循環(huán)的事情。數(shù)據(jù)處理完全是在庫內(nèi)部進行的。我們把這種思想叫作內(nèi)部迭代。第4章還會談到這些思想。使用集合的另一個頭疼之處是,想想看,要是交易量非常龐大,你要怎么處理這個巨大的列表呢?單個CPU根本搞不定這么大量的數(shù)據(jù),但你很可能已經(jīng)有了一臺多核計算機。理想情況下,你可能想讓這些CPU核共同分擔(dān)處理工作,以縮短處理時間。理論上來說,要是你有八個核,那并行起來,處理數(shù)據(jù)的速度應(yīng)該是單核的八倍。多核計算機所有新的臺式機和筆記本電腦都是多核的。它們不是僅有一個CPU,而是有四個、八個,甚至更多CPU,通常稱為核6。問題是,經(jīng)典的Java程序只能利用其中一個核,其他核的處理能力都浪費了。類似地,很多公司利用計算集群(用高速網(wǎng)絡(luò)連接起來的多臺計算機)來高效處理海量數(shù)據(jù)。Java8提供了新的編程風(fēng)格,可更好地利用這樣的計算機。Google的搜索引擎就是一個無法在單臺計算機上運行的代碼示例。它要讀取互聯(lián)網(wǎng)上的每個頁面并建立索引,將每個網(wǎng)頁上出現(xiàn)的每個詞都映射到包含該詞的網(wǎng)址上。然后,如果你用多個詞進行搜索,軟件就可以快速利用索引,給你一個包含這些詞的網(wǎng)頁集合。想想看,你會如何在Java中實現(xiàn)這個算法,哪怕是比Google小的引擎也需要你利用計算機上所有的核。6從某種意義上說,這個名字不太好。一塊多核芯片上的每個核都是一個五臟俱全的CPU。但“多核CPU”的說法很流行,所以我們就用核來指代各個CPU。多線程并非易事問題在于,通過多線程代碼來利用并行(使用先前Java版本中的ThreadAPI)并非易事。你得換一種思路:線程可能會同時訪問并更新共享變量。因此,如果沒有協(xié)調(diào)好7,那么數(shù)據(jù)可能會被意外改變。相比一步步執(zhí)行的順序模型,這個模型不太好理解8。比如,圖1-5就展示了如果沒有同步好,兩個線程同時向共享變量sum加上一個數(shù)時,可能會出現(xiàn)的問題。7傳統(tǒng)上是利用synchronized關(guān)鍵字,但是要是用錯了地方,就可能出現(xiàn)很多難以察覺的錯誤。Java8基于Stream的并行提倡很少使用synchronized的函數(shù)式編程風(fēng)格,它關(guān)注數(shù)據(jù)分塊而不是協(xié)調(diào)訪問。8啊哈,促使語言發(fā)展的一個動力源!圖1-5兩個線程對共享的sum變量做加法的一種可能方式。結(jié)果是105,而不是預(yù)想的108Java8也用StreamAPI(java.util.stream)解決了這兩個問題:集合處理時的模板化和晦澀,以及難以利用多核。這樣設(shè)計的第一個原因是,有許多反復(fù)出現(xiàn)的數(shù)據(jù)處理模式,類似于前一節(jié)所說的filterApples或SQL等數(shù)據(jù)庫查詢語言里熟悉的操作,如果庫中有這些就會很方便:根據(jù)標(biāo)準(zhǔn)篩選數(shù)據(jù)(比如較重的蘋果),提取數(shù)據(jù)(例如抽取列表中每個蘋果的重量字段),或給數(shù)據(jù)分組(例如,將一個數(shù)字列表分為奇數(shù)列表和偶數(shù)列表)等。第二個原因是,這類操作常??梢圆⑿?。例如,如圖1-6所示,在兩個CPU上篩選列表,可以讓一個CPU處理列表的前一半,另一個CPU處理后一半,這稱為分支步驟?。CPU隨后對各自的半個列表做篩選?。最后?,一個CPU會將兩個結(jié)果合并(Google搜索這么快就與此緊密相關(guān),當(dāng)然用的CPU遠遠不止兩個)。圖1-6將filter分支到兩個CPU上并合并結(jié)果到這里,我們只是說新的StreamAPI和Java現(xiàn)有的CollectionAPI的行為差不多,它們都能夠訪問數(shù)據(jù)項目的序列。不過,現(xiàn)在最好記住,Collection主要是為了存儲和訪問數(shù)據(jù),Stream則主要用于描述對數(shù)據(jù)的計算。這里的關(guān)鍵點在于,StreamAPI允許并提倡并行處理一個Stream中的元素。雖然乍看上去可能有點兒怪,但篩選一個Collection(將上一節(jié)的filterApples應(yīng)用在一個List上)的最快方法常常是將其轉(zhuǎn)換為Stream,進行并行處理,然后再轉(zhuǎn)換回List,下面列舉的串行和并行的例子都是如此。我們這里還只是說“幾乎免費的并行”,讓你稍微體驗一下,如何利用Stream和Lambda表達式順序或并行地從一個列表里篩選比較重的蘋果。順序處理:importstaticjava.util.stream.Collectors.toList;List<Apple>heavyApples=inventory.stream().filter((Applea)->a.getWeight()>150).collect(toList());并行處理:importstaticjava.util.stream.Collectors.toList;List<Apple>heavyApples=inventory.parallelStream().filter((Applea)->a.getWeight()>150).collect(toList());Java中的并行與無共享可變狀態(tài)大家都說在Java中并行很難,而且和synchronized相關(guān)的“玩意兒”都容易出問題。那Java8里面有什么“靈丹妙藥”呢?事實上有兩個。首先,庫會負(fù)責(zé)分塊,即把大的流分成幾個小的流,以便并行處理。其次,流提供的這個幾乎免費的并行,只有在傳遞給filter之類的庫方法的方法不會互動(比方說有可變的共享對象)時才能工作。但是其實這個限制對于程序員來說挺自然的,比如Apple::isGreenApple就是這樣。確實,雖然函數(shù)式編程中的函數(shù)的主要意思是“把函數(shù)作為一等值”,但它也常常隱含著第二層意思,即“執(zhí)行時在元素之間無互動”。第7章會詳細(xì)探討Java8中的并行數(shù)據(jù)處理及其特點。在加入所有這些新“玩意兒”改進Java的時候,Java8設(shè)計者發(fā)現(xiàn)的一個現(xiàn)實問題就是現(xiàn)有的接口也在改進。比如,Collections.sort方法真的應(yīng)該屬于List接口,但從來沒有放在后者里。理想情況下,你會希望做list.sort(comparator),而不是Collections.sort(list,comparator)。這看起來無關(guān)緊要,但是在Java8之前,你可能會更新一個接口,然后發(fā)現(xiàn)你把所有實現(xiàn)它的類也給更新了——簡直是邏輯災(zāi)難!這個問題在Java8里由默認(rèn)方法解決了。1.5默認(rèn)方法及Java模塊正如前文所介紹的,現(xiàn)代系統(tǒng)傾向于基于組件進行構(gòu)建,而這些組件可能源自第三方。歷史上,Java對此的支持非常薄弱,它只支持由幾個Java包組成的JAR文件,并且這些Java

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論