JavaRush /Java Blog /Random-TW /本書的翻譯。Java 中的函數式程式設計。第1章
timurnav
等級 21

本書的翻譯。Java 中的函數式程式設計。第1章

在 Random-TW 群組發布
我很樂意幫助您發現錯誤並提高翻譯品質。我翻譯是為了提高我的英語語言技能,如果你閱讀並尋找翻譯錯誤,那麼你會比我進步得更好。書的作者寫道,這本書假設了很多使用 Java 的經驗;說實話,我自己並不是特別有經驗,但我理解了書中的內容。這本書涉及一些很難手動解釋的理論。如果 wiki 上有不錯的文章,我會提供它們的鏈接,但為了更好地理解,我建議您自己搜索 Google。祝大家好運。:) 對於那些想要修改我的翻譯的人,以及那些覺得俄語讀起來太差的人,你可以在這裡下載原書。 目錄第1 章你好,Lambda 表達式- 目前正在閱讀第2 章使用集合- 開發中第3 章字符串、比較器和過濾器- 開發中第4 章使用Lambda 表達式進行開發- 開發中第5章使用資源- 開發中第6 章懶惰- 開發中開發 第 7 章 優化資源 - 開發中 第 8 章 使用 lambda 表達式佈局 - 開發中 第 9 章 整合 - 開發中

第 1 章 你好,Lambda 表達式!

我們的 Java 程式碼已準備好進行顯著的轉換。我們執行的日常任務變得更加簡單、輕鬆且更具表現力。Java 程式設計的新方法已在其他語言中使用了數十年。透過對 Java 的這些更改,我們可以編寫簡潔、優雅、富有表現力且錯誤更少的程式碼。我們可以使用它來輕鬆應用標準並以更少的程式碼行實現常見的設計模式。在本書中,我們使用每天遇到的問題的簡單範例來探索函數式程式設計風格。在我們深入研究這種優雅的風格和這種新的軟體開發方式之前,讓我們看看為什麼它更好。
改變你的想法
命令式風格是 Java 自語言誕生以來就賦予我們的風格。這種風格建議我們向 Java 描述我們希望該語言執行的每一步,然後我們只需確保忠實地遵循這些步驟即可。這很有效,但水平仍然很低。程式碼最終變得過於冗長,而我們經常想要一種更聰明的語言。然後我們可以聲明式地說出我們想要 什麼,而不是深入研究 如何做。感謝開發人員,Java 現在可以幫助我們做到這一點。讓我們看幾個範例來了解這些方法之間的優點和差異。
通常的方式
讓我們從熟悉的基礎知識開始,看看這兩個範例的實際應用。這使用命令式方法在城市集合中搜尋芝加哥 - 本書中的清單僅顯示程式碼片段。 boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); 程式碼的命令式版本是嘈雜的(這個字與它有什麼關係?)並且是低階的,有幾個可變的部分。首先,我們建立這個名為 find 的臭布爾標誌,然後迭代集合中的每個元素。如果我們找到了我們正在尋找的城市,我們將標誌設為 true並打破循環。最後我們將搜尋結果列印到控制台。
有一個更好的方法
作為一個細心的 Java 程式設計師,瀏覽這段程式碼可以將其變成更具表現力和更易於閱讀的內容,如下所示: System.out.println("Found chicago?:" + cities.contains("Chicago")); 這是聲明式風格的範例- contains() 方法可以幫助我們直接獲得所需的內容。
實際變化
這些更改將為我們的程式碼帶來相當大的改進:
  • 不用擔心可變變數
  • 循環迭代隱藏在幕後
  • 減少代碼混亂
  • 程式碼更加清晰,集中註意力
  • 阻抗較小;程式碼緊密追蹤業務意圖
  • 出錯的機會更少
  • 更容易理解和支持
超越簡單的案例
這是一個聲明性函數的簡單範例,用於檢查集合中是否存在元素;它已在 Java 中使用了很長時間。現在想像一下,不必為更進階的操作(例如解析檔案、使用資料庫、發出 Web 服務請求、建立多執行緒等)編寫命令式程式碼。現在,Java 使得編寫簡潔、優雅的程式碼​​成為可能,從而使錯誤變得更加困難,不僅是在簡單的操作中,而且在我們的整個應用程式中也是如此。
老辦法
讓我們來看另一個例子。我們正在創建一個包含價格的集合,並將嘗試多種方法來計算所有折扣價格的總和。 假設我們被要求匯總所有價值超過 20 美元的價格,並享受 10% 的折扣。讓我們先以通常的 Java 方式執行此操作。 這段程式碼對我們來說應該非常熟悉:首先我們建立一個可變變數 totalOfDiscountedPrices,我們將在其中儲存結果值。然後,我們循環訪問價格集合,選擇高於 20 美元的價格,獲取折扣價,並將該值添加到 totalOfDiscountedPrices中。最後,我們顯示考慮折扣後所有價格的總和。以下是控制台的輸出內容 final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
折扣總價:67.5
可以用,但是程式碼看起來很亂。但這不是我們的錯,我們使用了可用的東西。該代碼的等級相當低 - 它受到對原語的痴迷(谷歌它,有趣的東西)並且它違背了 單一責任原則。我們這些在家工作的人應該讓這樣的程式碼遠離那些立志成為程式設計師的孩子們的眼睛,這可能會驚動他們脆弱的心靈,準備好面對“這是你為了生存而必須做的事情嗎?”的問題。
有一個更好的方法,另一種
現在我們可以做得更好,更好。我們的程式碼可能類似於規範要求。這將幫助我們縮小業務需求和實現它們的程式碼之間的差距,進一步減少需求被誤解的可能性。讓我們在更高的抽象層級上工作,而不是建立變數然後重複更改它,例如下面的清單。 final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); 讓我們大聲讀出來 - 價格過濾器大於 20,使用“價格”鍵進行映射(創建“鍵”“值”對),價格包括折扣,然後將它們相加
- 譯者的註解是指在閱讀程式碼時出現在您腦海中的單字 .filter(price ->price.compareTo(BigDecimal.valueOf(20)) > 0)
程式碼按照我們所讀到的相同邏輯順序一起執行。程式碼被縮短了,但我們使用了 Java 8 中的一大堆新東西。首先,我們在 價格列表上呼叫了stream()方法。這為自訂迭代器打開了大門,該迭代器具有豐富的便利功能,我們將在稍後討論。 我們沒有直接循環價格列表中的所有值,而是使用幾種特殊的方法,例如 filter()map()。與我們在 Java 和 JDK 中使用的方法不同,這些方法採用匿名函數(lambda 表達式)作為括號中的參數。我們稍後會更詳細地研究它。 透過呼叫reduce()方法,我們計算 map()方法中獲得的值(折扣價)的總和。 循環的隱藏方式與使用contains()方法時的方式相同。 然而, filter()map()方法更加複雜。 對於價格列表中的每個價格,他們調用傳遞的 lambda 函數並將其保存到新集合中。對此集合呼叫 reduce()方法以產生最終結果。以下是控制台的輸出內容
折扣總價:67.5
變化
以下是相對於通常方法的變化:
  • 代碼賞心悅目,不雜亂。
  • 沒有低階操作
  • 更容易改進或改變邏輯
  • 迭代由方法庫控制
  • 高效、惰性循環評估
  • 根據需要更容易並行化
稍後我們將討論 Java 如何提供這些改進。
蘭姆達來救援:)
Lambda 是將我們從命令式程式設計的麻煩中解放出來的功能關鍵。透過改變我們程式設計的方式,借助Java的最新特性,我們可以寫出的程式碼不僅優雅簡潔,而且不易出錯,效率更高,更容易優化、改進、製作多執行緒。
從函數式程式設計中獲益匪淺
函數式程式設計風格具有更高的 信噪比;我們編寫的程式碼行更少,但每行或表達式執行更多功能。與命令式相比,我們從函數式程式碼版本中獲得的收益很少:
  • 我們避免了不必要的變數更改或重新分配,這些更改或重新分配是錯誤的來源,並且使得同時處理來自不同線程的程式碼變得困難。在命令式版本中,我們在整個循環中為totalOfDiscountedPrices變數設定不同的值。在函數式版本中,程式碼中的變數沒有明確變更。更少的更改會導致程式碼中的錯誤更少。
  • 程式碼的函數版本更容易並行化。即使map()方法中的計算很長,我們也可以並行運行它們而不必擔心。如果我們從不同的執行緒存取命令式程式碼,我們將需要擔心同時更改totalOfDiscountedPrices變數。在函數式版本中,我們只有在完成所有更改後才訪問變量,這使我們不必擔心程式碼的線程安全性。
  • 程式碼更具表現力。我們不需要分幾個步驟執行程式碼 - 建立並用虛擬值初始化變數、循環存取價格清單、向變數添加折扣價格等等 - 我們只需要求清單的 map() 方法返回另一個清單折扣價格並將它們相加。
  • 函數式風格更加簡潔:比命令式版本需要更少的程式碼行。更緊湊的程式碼意味著更少的編寫、閱讀和維護。
  • 一旦您了解其語法,該程式碼的函數版本就非常直觀且易於理解。map()方法將傳遞的函數(計算折扣價格)應用於集合的每個元素,並產生包含結果的集合,如下圖所示。

圖1-map方法將傳遞的函數應用到集合的每個元素
在 lambda 表達式的支援下,我們可以充分利用 Java 函數式程式設計的威力。如果我們掌握了這種風格,我們就可以創建更具表現力、更簡潔的程式碼,並且更改和錯誤更少。以前,Java 的主要優點之一是它對物件導向範例的支援。而且函數式風格並不與OOP相矛盾。從命令式程式設計轉向聲明式程式設計的真正卓越。借助 Java 8,我們可以非常有效地將函數式程式設計與物件導向風格結合。我們可以繼續將 OO 風格應用於物件、它們的範圍、狀態和關係。此外,我們還可以將行為和狀態變化、業務流程和資料處理建模為一系列功能集。
為什麼要以函數式風格編寫程式碼?
我們已經看到了函數式程式設計風格的整體優勢,但是這種新風格值得學習嗎?這只是語言上的微小變化還是改變我們的生活?在浪費時間和精力之前,我們必須先找到這些問題的答案。編寫 Java 程式碼並不困難;該語言的語法很簡單。我們對熟悉的函式庫和 API 感到滿意。真正需要我們花精力編寫和維護程式碼的是我們使用Java進行開發的典型企業應用程式。我們需要確保其他程式設計師在正確的時間關閉與資料庫的連接,確保他們不會持有資料庫或執行事務的時間超過必要的時間,確保他們在正確的層級完全捕獲異常,確保他們正確地應用和釋放鎖。 ...這張紙可以持續很長時間。上述每一個論點單獨來看都沒有什麼分量,但是當與固有的實現複雜性相結合時,它就變得勢不可擋、耗時且難以實現。如果我們可以將這些複雜性封裝成能夠很好地管理它們的小程式碼片段,會怎麼樣?那麼我們就不會不斷地花費精力去執行標準。這將帶來很大的優勢,所以讓我們看看函數式風格如何提供幫助。
喬問
短*代碼是否僅僅意味著更少的代碼字母?
* 我們談論的是「簡潔」​​這個詞,它描述了使用 lambda 表達式的程式碼的函數式風格
在這種情況下,程式碼應該簡潔,沒有多餘的裝飾,並減少直接影響,以更有效地傳達意圖。這些都是深遠的好處。編寫程式碼就像將配料放在一起:使其簡潔就像為其添加醬汁。有時編寫這樣的程式碼需要花費更多的精力。需要閱讀的代碼較少,但它使代碼更加透明。縮短程式碼時保持程式碼清晰非常重要。簡潔的程式碼類似於設計技巧。此代碼需要較少的手鼓跳舞。這意味著我們可以快速實施我們的想法,如果它們有效,我們可以繼續前進,如果它們不符合期望,我們可以放棄它們。
類固醇的迭代
我們使用迭代器來處理物件列表,以及使用集合和映射。我們在 Java 中使用的迭代器是我們所熟悉的;雖然它們很原始,但並不簡單。它們不僅佔用大量程式碼,而且編寫起來也相當困難。我們如何迭代集合的所有元素?我們可以使用 for 迴圈。我們如何從集合中選擇一些元素?使用相同的 for 循環,但使用一些額外的可變變量,這些變量需要與集合中的某些內容進行比較。那麼,選擇特定值後,我們如何對單一值進行操作,例如最小值、最大值或某個平均值?再次循環,再次出現新的變數。這讓人想起諺語「只見樹木,不見森林」(原文使用了與迭代相關的雙關語,意思是「一切都已完成,但並非一切都成功」——譯者註)。jdk 現在為各種語句提供內部迭代器:一個用於簡化循環,一個用於綁定所需的結果依賴項,一個用於過濾輸出值,一個用於返回值,以及幾個用於獲取最小值、最大值、平均值等的便利函數。此外,這些操作的功能可以非常容易地組合,這樣我們就可以組合不同的操作集,以更輕鬆、更少的程式碼來實現業務邏輯。完成後,程式碼將更容易理解,因為它按照問題所需的順序創建邏輯解決方案。我們將在本書的第 2 章和後面的部分中查看此類程式碼的一些範例。
演算法應用
演算法驅動企業應用程式。例如我們需要提供一個需要權限檢查的操作。我們必須確保交易快速完成並正確完成檢查。這類任務通常會簡化為非常普通的方法,如下面的清單所示: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); 這種方法有兩個問題。首先,這通常會導致開發工作量加倍,進而導致維護應用程式的成本增加。其次,很容易錯過該應用程式程式碼中可能拋出的異常,從而危及交易的執行和檢查的通過。我們可以使用適當的 try-finally 區塊,但是每次有人觸及這段程式碼時,我們都需要重新檢查程式碼的邏輯是否沒有被破壞。否則,我們可以放棄工廠並徹底改變整個程式碼。我們可以將處理程式碼傳送到管理良好的函數,而不是接收交易,例如下面的程式碼。 runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); 這些小的變更加起來可以節省大量成本。狀態檢查和應用程式檢查的演算法被賦予了新的抽象級別,並使用 runWithinTransaction()方法進行封裝。在此方法中,我們放置了一段應在事務上下文中執行的程式碼。我們不再需要擔心忘記做某事或是否在正確的位置捕獲了異常。演算法函數可以解決這個問題。這個問題將在第 5 章中更詳細地討論。
演算法擴展
演算法的使用越來越頻繁,但為了讓它們充分用於企業應用程式開發,需要擴展它們的方法。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION