熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

分享關於Java 語言中的函數編程

2022-06-13   來源: Java核心技術 

  如果您從事大型企業項目開發您就會熟悉編寫模塊化代碼的好處良構的模塊化的代碼更容易編寫調試理解和重用Java 開發人員的問題是函數編程范型長期以來只是通過像 HaskellSchemeErlang 和 Lisp 這樣的特殊語言實現的在本文中作者 Abhijit Belapurkar 展示了如何使用像閉包(closure)和 高階函數(higher order function)這樣的函數編程結構在 Java 語言中編寫良構的模塊化的代碼
  
  Java 語言中常被忽視的一個方面是它被歸類為一種命令式(imperative)編程語言命令式編程雖然由於與 Java 語言的關聯而相當普及但是並不是惟一可用的編程風格也不總是最有效的在本文中我將探討在 Java 開發實踐中加入不同的編程方法 ── 即函數編程(FP)
  
  命令式編程是一種用程序狀態描述計算的方法使用這種范型的編程人員用語句改變程序狀態這就是為什麼像 Java 這樣的程序是由一系列讓計算機執行的命令 (或者語句) 所組成的 另一方面 函數編程是一種強調表達式的計算而非命令的執行的一種編程風格表達式是用函數結合基本值構成的它類似於用參數調用函數
  
  本文將介紹函數編程的基本特點但是重點放在兩個特別適用於 Java 開發框架的元素閉包和高階函數如果您曾經使用過像 PythonRuby 或者 Groovy (請參閱 參考資料) 這樣的敏捷開發語言那麼您就可能已經遇到過這些元素在這裡您將看到在 Java 開發框架中直接使用它們會出現什麼情況我將首先對函數編程及其核心元素做一個簡短的概念性的綜述然後用常用的編程場景展示用結構化的方式使用閉包和高階函數會給 Java 代碼帶來什麼好處
  
  什麼是函數編程?
  
  在經常被引用的論文 Why Functional Programming Matters(請參閱 參考資料) 中作者 John Hughes 說明了模塊化是成功編程的關鍵而函數編程可以極大地改進模塊化在函數編程中編程人員有一個天然框架用來開發更小的更簡單的和更一般化的模塊 然後將它們組合在一起函數編程的一些基本特點包括
  
  支持閉包和高階函數
  支持懶惰計算(lazy evaluation)
  使用遞歸作為控制流程的機制
  加強了引用透明性
  沒有副作用
  
  我將重點放在在 Java 語言中使用閉包和高階函數上但是首先對上面列出的所有特點做一個概述
  
  閉包和高階函數
  
  函數編程支持函數作為第一類對象有時稱為 閉包或者 仿函數(functor)對象實質上閉包是起函數的作用並可以像對象一樣操作的對象與此類似FP 語言支持 高階函數高階函數可以用另一個函數(間接地用一個表達式) 作為其輸入參數在某些情況下它甚至返回一個函數作為其輸出參數這兩種結構結合在一起使得可以用優雅的方式進行模塊化編程這是使用 FP 的最大好處
  
  命令式編程
  
  命令式編程這個名字是從自然語言(比如英語)的 祈使語氣(imperative mood)衍生出來的在這種語氣中宣布命令並按照執行除 Java 語言之外C 和 C++ 是另外兩種廣泛使用的符合命令式風格的高級編程語言
  
  懶惰計算
  
  除了高階函數和仿函數(或閉包)的概念FP 還引入了 懶惰計算的概念在懶惰計算中表達式不是在綁定到變量時立即計算而是在求值程序需要產生表達式的值時進行計算延遲的計算使您可以編寫可能潛在地生成無窮輸出的函數因為不會計算多於程序的其余部分所需要的值所以不需要擔心由無窮計算所導致的 outofmemory 錯誤一個懶惰計算的例子是生成無窮 Fibonacci 列表的函數但是對 第 n 個Fibonacci 數的計算相當於只是從可能的無窮列表中提取一項
  
  遞歸
  
  FP 還有一個特點是用遞歸做為控制流程的機制例如Lisp 處理的列表定義為在頭元素後面有子列表這種表示法使得它自己自然地對更小的子列表不斷遞歸
  
  關於實現庫
  
  我使用了由 Apache Commons Functor 項目提供的庫構建本文使用的例子Apache Commons Functor 庫包括大量基本構造可以在涉及閉包和高階函數的復雜使用場景中重復使用當然可以使用不同的實現(如 Java Generic LibrariesMango 或者 Generic Algorithms for Java)而不會對在本文中所討論和展示的概念有影響盡管您必須下載和使用 Apache Commons Functor 庫才能演示這裡的例子
  
  引用透明性
  
  函數程序通常還加強 引用透明性即如果提供同樣的輸入那麼函數總是返回同樣的結果就是說表達式的值不依賴於可以改變值的全局狀態這使您可以從形式上推斷程序行為因為表達式的意義只取決於其子表達式而不是計算順序或者其他表達式的副作用這有助於驗證正確性簡化算法甚至有助於找出優化它的方法
  
  副作用
  
  副作用是修改系統狀態的語言結構因為 FP 語言不包含任何賦值語句變量值一旦被指派就永遠不會改變而且調用函數只會計算出結果 ── 不會出現其他效果因此FP 語言沒有副作用
  
  這些基本描述應足以讓您完成本文中的函數編程例子有關這個主題的更多參考資料請參閱 參考資料一節
  
  Java 語言中的函數編程
  
  不管是否相信在 Java 開發實踐中您可能已經遇到過閉包和高階函數盡管當時您可能沒有意識到例如許多 Java 開發人員在匿名內部類中封閉 Java 代碼的一個詞匯單元(lexical unit)時第一次遇到了 閉包這個封閉的 Java 代碼單元在需要時由一個 高階函數 執行例如清單 中的代碼在一個類型為 javalangRunnable 的對象中封閉了一個方法調用
  
  清單 隱藏的閉包
  
  Runnable worker = new Runnable()
  {
  public void run()
  {
  parseData();
  }
  };
  
  方法 parseData 確實 封閉(因而有了名字 閉包)在 Runnable 對象的實例 worker 中它可以像數據一樣在方法之間傳遞並可以在任何時間通過發送消息(稱為 run ) 給 worker 對象而執行
  
  更多的例子
  
  另一個在面向對象世界中使用閉包和高階函數的例子是 Visitor 模式如果還不熟悉這種模式請參閱 參考資料以了解更多有關它的內容基本上Visitor 模式展現一個稱為 Visitor 的參與者該參與者的實例由一個復合對象(或者數據結構)接收並應用到這個數據結構的每一個構成節點Visitor 對象實質上 封閉 了處理節點/元素的邏輯使用數據結構的 accept (visitor) 方法作為應用邏輯的高階函數
  
  通過使用適當不同的 Visitor 對象(即閉包)可以對數據結構的元素應用完全不同的處理邏輯與此類似可以向不同的高階函數傳遞同樣的閉包以用另一種方法處理數據結構(例如這個新的高階函數可以實現不同邏輯用於遍歷所有構成元素)
  
  類 javautilsCollections 提供了另一個例子這個類在版本 以後成為了 Java SDK 的一部分它提供的一種實用程序方法是對在 javautilList 中包含的元素排序不過它使調用者可以將排序列表元素的邏輯封裝到一個類型為 javautilComparator 的對象中其中 Comparator 對象作為第二個參數傳遞給排序方法
  
  在內部 sort 方法的引用實現基於 合並排序(mergesort) 算法它通過對順序中相鄰的對象調用 Comparator 對象的 compare 方法遍歷列表(list)中的元素在這種情況下 Comparator 是閉包而 Collectionssort 方法是高階函數這種方式的好處是明顯的可以根據在列表中包含的不同對象類型傳遞不同的 Comparator 對象(因為如何比較列表中任意兩個對象的邏輯安全地封裝在 Comparator 對象中)與此類似 sort 方法的另一種實現可以使用完全不同的算法(比如說 快速排序(quicksort)) 並仍然重復使用同樣的 Comparator 對象將它作為基本函數應用到列表中兩個元素的某種組合
  
  創建閉包
  
  廣意地說有兩種生成閉包的技術使用閉包的代碼可以等效地使用這兩種技術創建閉包後可以以統一的方式傳遞它也可以向它發送消息以讓它執行其封裝的邏輯因此技術的選擇是偏好的問題在某些情況下也與環境有關
  
  最後一次要求下載!
  
  從這以後的討論將結合基於 Apache Commons Functor 庫的例子如果您還沒有下載這個庫應當 現在就下載我將假定您可以訪問 Javadocs 所帶的 Apache 庫因此對單獨的庫類不再做過多的說明
  
  在第一種技術 表達式特化(expression specialization)中由基礎設施為閉包提供一個一般性的接口通過編寫這個接口的特定實現創建具體的閉包在第二種技術 表達式合成(expression composition) 中基礎設施提供實現了基本一元/二元/三元//n 元操作(比如一元操作符 not 和二元操作符 and / or )的具體幫助類在這裡新的閉包由這些基本構建塊的任意組合創建而成
  
  我將在下面的幾節中詳細討論這兩種技術
  
  表達式特化
  
  假定您在編寫一個在線商店的應用程序商店中可提供的商品用類 SETLItem 表示每一件商品都有相關的標簽價格 SETLItem 類提供了名為 getPrice 的方法對商品實例調用這個方法時會返回該商品的標簽價格
  
  如何檢查 item 的成本是否不低於 item 呢?在 Java 語言中一般要編寫一個像這樣的表達式
  
  assert(itemgetPrice() >= itemgetPrice());
  
  像這樣的表達式稱為 二元謂詞(binary predicate) 二元是因為它取兩個參數而 謂詞 是因為它用這兩個參數做一些事情並生成布爾結果不過要注意只能在執行流程中執行上面的表達式它的輸出取決於 item 和 item 在特定瞬間的值從函數編程的角度看這個表達式還不是一般性的邏輯就是說它不能不管執行控制的當前位置而隨心所欲地傳遞並執行
  
  為了使二元謂詞發揮作用必須將它封裝到一個對象中通過 特化(specializing) 一個稱為 BinaryPredicate 的接口做到這一點這個接口是由 Apache Functor 庫提供的如清單 所示
  
  清單 表達式特化方法
  
  package syssetlfp;
  public class SETLItem
  {
  private String name;
  private String code;
  private int price;
  private String category;
  
  public int getPrice()
  {
  return price;
  }
  
  public void setPrice(int inPrice)
  {
  price = inPrice;
  }
  
  public String getName()
  {
  return name;
  }
  
  public void setName(String inName)
  {
  name = inName;
  }
  
  public String getCode()
  {
  return code;
  }
  
  public void setCode(String inCode)
  {
  code = inCode;
  }
  
  public String getCategory()
  {
  return category;
  }
  
  public void setCategory(String inCategory)
  {
  category = inCategory;
  }
  }
  
  package syssetlfp;
  import javautilComparator;
  public class PriceComparator implements Comparator
  {
  public int compare (Object o Object o)
  {
  return (((SETLItem)o)getPrice()((SETLItem)o)getPrice());
  }
  }
  
  package syssetlfp;
  import monsfunctor*;
  import parator*;
  import javautil*;
  public class TestA
  {
  public static void main(String[] args)
  {
  try
  {
  Comparator pc = new PriceComparator();
  BinaryPredicate bp = new IsGreaterThanOrEqual(pc);
  SETLItem item = new SETLItem();
  itemsetPrice();
  SETLItem item = new SETLItem();
  itemsetPrice();
  if (bptest(item item))
  Systemoutprintln(Item costs more than Item!);
  else
  Systemoutprintln(Item costs more than Item!);
  
  SETLItem item = new SETLItem();
  itemsetPrice();
  if (bptest(item item))
  Systemoutprintln(Item costs more than Item!);
  else
  Systemoutprintln(Item costs more than Item!);
  }
  catch (Exception e)
  {
  eprintStackTrace();
  }
  }
  }
  
  BinaryPredicate 接口以由 Apache Functor 庫提供的 IsGreaterThanOrEqual 類的形式特化 PriceComparator 類實現了 javautilComparator 接口並被作為輸入傳遞給 IsGreaterThanOrEqual 類收到一個 test 消息時 IsGreaterThanOrEqual 類自動調用 PriceComparator 類的 compare 方法 compare 方法預期接收兩個 SETLItem 對象相應地它返回兩個商品的價格差 compare 方法返回的正值表明 item 的成本不低於 item
  
  初看之下對一個相當基本的操作要做很多的工作那它有什麼好處呢?特化 BinaryPredicate 接口(而不是編寫 Java 比較表達式) 使您無論在何時何地都可以比較任意兩個商品的價格可以將 bp 對象作為數據傳遞並向它發送消息以在任何時候使用這兩個參數的任何值來執行它(稱為 test )
  
  表達式合成
  
  表達式合成是得到同樣結果的一種稍有不同的技術考慮計算特定 SETLItem 的淨價問題要考慮當前折扣和銷售稅率清單 列出了這個問題基於仿函數的解決方式
  
  清單 表達式合成方法
  
  package syssetlfp;
  import monsfunctorBinaryFunction;
  import monsfunctorUnaryFunction;
  import monsfunctoradapterLeftBoundFunction;
  public class Multiply implements BinaryFunction
  {
  public Object evaluate(Object left Object right)
  {
  return new Double(((Double)left)doubleValue() * ((Double)right)doubleValue());
  }
  }
  
  package syssetlfp;
  import monsfunctor*;
  import posite*;
  import monsfunctoradapter*;
  import monsfunctorUnaryFunction;
  public class TestB
  {
  public static void main(String[] args)
  {
  try
  {
  double discountRate = ;
  double taxRate=;
  SETLItem item = new SETLItem();
  itemsetPrice();
  UnaryFunction calcDiscountedPrice =
  new RightBoundFunction(new Multiply() new Double(discountRate));
  UnaryFunction calcTax =
  new RightBoundFunction(new Multiply() new Double(+taxRate));
  CompositeUnaryFunction calcNetPrice =
  new CompositeUnaryFunction(calcTax calcDiscountedPrice);
  Double netPrice = (Double)calcNetPriceevaluate(new Double(itemgetPrice()));
  Systemoutprintln(The net price is: + netPrice);
  }
  catch (Exception e)
  {
  eprintStackTrace();
  }
  }
  }
  
  BinaryFunction 類似於前面看到的 BinaryPredicate 是一個由 Apache Functor 提供的一般化仿函數(functor)接口 BinaryFunction 接口有兩個參數並返回一個 Object 值類似地 UnaryFunction 是一個取一個 Object 參數並返回一個 Object 值的仿函數接口
  
  RightBoundFunction 是一個由 Apache 庫提供的適配器類它通過使用常量右參數(rightside argument)將 BinaryFunction 適配給 UnaryFunction 接口在一個參數中收到相應的消息( evaluate ) 時它在內部用兩個參數發送一個 evaluate 消息給正在適配的 BinaryFunction ── 左邊的是發送給它的參數右邊的是它知道的常量您一定會猜到名字 RightBoundFunction 來自於常量值是作為第二個 (右邊) 參數傳遞這一事實(是的Apache 庫還提供了一個 LeftBoundFunction 其中常量是作為第一個參數或者左參數傳遞的
  
  用於雙精度相乘的特化的 BinaryFunction
  
  清單 顯示了名為 Multiply 的特化的 BinaryFunction 它取兩個 Double 作為輸入並返回一個新的由前兩個雙精度值相乘而得到 Double
  
  在 calcDiscountedRate 中實例化了一個新的 RightBoundFunction 它通過用 ( discountRate) 作為其常量第二參數將二元 Multiply 函數適配為一元接口
  
  結果可以用一個 Double 參數向 calcDiscountRate 發送一個名為 evaluate 的消息在內部輸入參數 Double 乘以 calcDiscountRate 對象本身包含的常量值
  
  與此類似在 calcTaxRate 中實例化了一個新的 RightBoundFunction 它通過用 ( + taxRate) 作為其第二個常量參數將二元 Multiply 函數適配為一元接口結果可以用一個 Double 參數向 calcTaxRate 發送一個名為 evaluate 的消息在內部輸入參數 Double 乘以 calcTaxRate 對象本身包含的常量值
  
  這種將多參數的函數重新編寫為一個參數的函數的合成(composition)技術也稱為 currying
  
  合成魔術在最後的時候就發揮作用了實質上計算對象淨價的算法是首先計算折扣價格(使用 calcDiscountRate 仿函數)然後通過在上面加上銷售稅(用 calcSalesTax 仿函數)計算淨價就是說需要組成一個函數在內部調用第一個仿函數並將計算的輸出流作為第二個仿函數的計算的輸入Apache 庫提供了用於這種目的的一個內置仿函數稱為 CompositeUnaryFunction
  
  在清單 CompositeUnaryFunction 實例化為變量 calcNetPrice 作為 calcDiscountRate 和 calcSalesTax 仿函數的合成與前面一樣將可以向其他函數傳遞這個對象其他函數也可以通過向它發送一個包含商品參數的 evaluate 消息要求它計算這種商品的淨價
  
  一元與二元合成
  
  在清單 您看到了 一元合成的一個例子其中一個一元仿函數的結果是另一個的輸入另一種合成稱為 二元合成作為 evaluate 消息的一部分需要傳遞兩個一元仿函數的結果作為二元仿函數的參數
  
  清單 是說明二元合成的必要性和風格的一個例子假定希望保證商店可以給出的最大折扣有一個最大限度因此必須將作為 calcDiscount 仿函數計算結果得到的折扣量與 cap 值進行比較並取最小值作為計算出的折扣價格折扣價格是通過用標簽價減去實際的折扣而計算的
  
  清單 二元合成
  
  package syssetlfp;
  import monsfunctorBinaryFunction;
  public class Subtract implements BinaryFunction
  {
  public Object evaluate(Object left Object right)
  {
  return new Double(((Double)left)doubleValue() ((Double)right)doubleValue());
  }
  }
  
  package syssetlfp;
  import monsfunctorBinaryFunction;
  import monsfunctorUnaryFunction;
  public class BinaryFunctionUnaryFunction implements UnaryFunction
  {
  private BinaryFunction function;
  
  public BinaryFunctionUnaryFunction(BinaryFunction f)
  {
  function=f;
  }
  
  public Object evaluate(Object obj)
  {
  return functionevaluate(objobj);
  }
  }
  
  package syssetlfp;
  import monsfunctor*;
  import posite*;
  import monsfunctoradapter*;
  import monsfunctorUnaryFunction;
  import reConstant;
  import paratorMin;
  public class TestC
  {
  public static void main(String[] args)
  {
  double discountRate = ;
  double taxRate=;
  double maxDiscount = ;
  SETLItem item = new SETLItem();
  itemsetPrice();
  UnaryFunction calcDiscount =
  new RightBoundFunction(new Multiply() new Double(discountRate));
  Constant cap = new Constant(new Double(maxDiscount));
  BinaryFunction calcActualDiscount =
  new UnaryCompositeBinaryFunction (new Min() calcDiscount cap);
  BinaryFunctionUnaryFunction calcActualDiscountAsUnary =
  new BinaryFunctionUnaryFunction(calcActualDiscount);
  BinaryFunction calcDiscountedPrice =
  new UnaryCompositeBinaryFunction (new Subtract() new Identity() calcActualDiscountAsUnary);
  BinaryFunctionUnaryFunction calcDiscountedPriceAsUnary =
  new BinaryFunctionUnaryFunction(calcDiscountedPrice);
  UnaryFunction calcTax =
  new RightBoundFunction(new Multiply() new Double(+taxRate));
  CompositeUnaryFunction calcNetPrice =
  new CompositeUnaryFunction(calcTax calcDiscountedPriceAsUnary);
  Double netPrice = (Double)calcNetPriceevaluate(new Double(itemgetPrice()));
  Systemoutprintln(The net price is: + netPrice);
  }
  }
  
  通過首先觀察所使用的 Apache Functor 庫中的三個標准仿函數開始分析和理解這段代碼
  
  UnaryCompositeBinaryFunction 仿函數取一個二元函數和兩個一元函數作為輸入首先計算後兩個函數它們的輸出作為輸入傳遞給二元函數在清單 中對二元合成使用這個仿函數兩次
  
  Constant 仿函數的計算總是返回一個常量值(即在其構造時輸入的值)不管以後任何計算消息中傳遞給它的參數是什麼值在清單 變量 cap 的類型為 Constant 並總是返回最大折扣數量
  
  Identity 仿函數只是返回作為 evaluate 消息的輸入參數傳遞給它的這個對象作為輸出清單 顯示 Identity 仿函數的一個實例該仿函數是在創建 calcDiscountedPrice 時作為一個一元仿函數創建和傳遞的同時在清單 evaluate 消息包含標簽價格作為其參數這樣 Identity 仿函數就返回標簽價格作為輸出
  
  第一個二元合成在用計算 calcDiscount (通過對標簽價格直接應用折扣率)和 cap 的 UnaryCompositeBinaryFunction 設置變量 calcActualDiscount 時是可見的這兩個一元仿函數計算的輸出傳遞給稱為 Min 的內置二元仿函數它比較這兩者並返回其中最小的值
  
  這個例子顯示了定制類 BinaryFunctionUnaryFunction 這個類適配一個二元仿函數使它像一元仿函數的接口就是說當這個類接收一個帶有一個參數的 evaluate 消息時它在內部發送 (向其封裝的二元函數)一個 evaluate 消息它的兩個參數是作為輸入接收的同一個對象因為 calcActualDiscount 是二元函數所以通過類型為 BinaryFunctionUnaryFunction 的 calcActualDiscountAsUnary 實例將它包裝到一個一元仿函數接口中很快就可以看到包裝 calcActualDiscount 為一元仿函數的理由
  
  當用 UnaryCompositeBinaryFunction 設置變量 calcDiscountedPrice 時發生第二個二元合成 UnaryCompositeBinaryFunction 向新的 Identity 實例和 calcActualDiscountAsUnary 對象發送 evaluation 消息這兩個消息的輸入參數都是標簽價格
  
  這兩個計算(它們分別得出標簽價格和實際的折扣值)的輸出傳遞給名為 Subtract 的定制二元仿函數當向後一個對象發送 evaluate 消息時它立即計算並返回兩個參數之間的差距(這是商品的折扣價)這個二元仿函數也用定制的 BinaryFunctionUnaryFunction 包裝為一個名為 calcDiscountedPriceAsUnary 的一元仿函數對象
  
  與前面的情況一樣代碼通過兩個 calcTax 一元仿函數(也在清單 中遇到)和 calcDiscountedPriceAsUnary (在前面一段中描述)創建 CompositeUnaryFunction 而以一個一元合成完成這樣得到的 calcNetPrice 變為接收一個 evaluate 消息和一個參數(所討論商品的標簽價格)而在內部首先用這個參數計算 calcDiscountedPriceAsUnary 仿函數然後用前一個計算的輸出作為參數計算 calcTax 仿函數
  
  使用閉包實現業務規則
  
  Apache Library 提供了各種不同的內置一元和二元仿函數它使得將業務邏輯編寫為可以傳遞並且可以用不同的參數在不同的位置執行的對象變得非常容易在後面幾節中我將使用一個簡單的例子展示對一個類似問題的函數編程方式
  
  假定一個特定的商品是否可以有折扣取決於該商品的類別和定價具體說只有 Category A 中定價高於 美元和 Category B 中定價高於 美元的商品才有資格打折清單 中的代碼顯示了一個名為 isEligibleForDiscount 的業務規則對象 ( UnaryPredicate )如果用一個 item 對象作為參數發送 evaluate 消息將返回一個表明是否可以對它打折的 Boolean
  
  清單 一個函數業務規則對象
  
  package syssetlfp;
  import monsfunctorBinaryPredicate;
  import monsfunctorUnaryPredicate;
  public class BinaryPredicateUnaryPredicate implements UnaryPredicate
  {
  private BinaryPredicate bp;
  
  public BinaryPredicateUnaryPredicate(BinaryPredicate prd)
  {
  bp=prd;
  }
  
  public boolean test(Object obj)
  {
  return bptest(objobj);
  }
  }
  
  package syssetlfp;
  import monsfunctor*;
  import posite*;
  import monsfunctoradapter*;
  import monsfunctorUnaryFunction;
  import reConstant;
  import reIsEqual;
  import paratorIsGreaterThanOrEqual;
  import paratorMin;
  import reIdentity;
  public class TestD
  {
  public static void main(String[] args)
  {
  SETLItem item = new SETLItem();
  itemsetPrice();
  itemsetCategory(A);
  
  SETLItem item = new SETLItem();
  itemsetPrice();
  itemsetCategory(A);
  
  SETLItem item = new SETLItem();
  itemsetPrice();
  itemsetCategory(B);
  
  UnaryFunction getItemCat =
  new UnaryFunction()
  {
  public Object evaluate (Object obj)
  {
  return ((SETLItem)obj)getCategory();
  }
  };
  
  UnaryFunction getItemPrice =
  new UnaryFunction()
  {
  public Object evaluate (Object obj)
  {
  return new Double(((SETLItem)obj)getPrice());
  }
  };
  
  Constant catA = new Constant(A);
  Constant catB = new Constant(B);
  Constant usd = new Constant(new Double());
  Constant usd = new Constant(new Double());
  
  BinaryPredicateUnaryPredicate belongsToCatA = new BinaryPredicateUnaryPredicate
  (new UnaryCompositeBinaryPredicate(new IsEqual() getItemCat catA));
  BinaryPredicateUnaryPredicate belongsToCatB = new BinaryPredicateUnaryPredicate
  (new UnaryCompositeBinaryPredicate(new IsEqual() getItemCat catB));
  
  BinaryPredicateUnaryPredicate moreThanUSD = new BinaryPredicateUnaryPredicate
  (new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual() getItemPrice usd));
  BinaryPredicateUnaryPredicate moreThanUSD = new BinaryPredicateUnaryPredicate
  (new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual() getItemPrice usd));
  
  UnaryOr isEligibleForDiscount = new UnaryOr(new UnaryAnd(belongsToCatA moreThanUSD)
  new UnaryAnd(belongsToCatB moreThanUSD));
  
  if (isEligibleForDiscounttest(item))
  Systemoutprintln(Item # is eligible for discount!);
  else
  Systemoutprintln(Item # is not eligible for discount!);
  
  if (isEligibleForDiscounttest(item))
  Systemoutprintln(Item # is eligible for discount!);
  else
  Systemoutprintln(Item # is not eligible for discount!);
  
  if (isEligibleForDiscounttest(item))
  Systemoutprintln(Item # is eligible for discount!);
  else
  Systemoutprintln(Item # is not eligible for discount!);
  }
  }
  
  使用 ComparableComparator
  
  清單 中可能注意到的第一件事是我利用了名為 isEqual (用於檢查所說商品的類別是否等於 A或者B ) 和 isGreaterThanOrEqual (用於檢查所述商品的定價是否大於或者等於指定值對於 Category A 商品是 對於 Category B 商品是 ) 的內置二元謂詞仿函數
  
  您可能還記得在 清單 原來必須傳遞 PriceComparator 對象(封裝了比較邏輯)以使用 isGreaterThanOrEqual 仿函數進行價格比較不過在清單 不顯式傳遞這個 Comparator 對象如何做到不需要它? 技巧就是在沒有指定該對象時 isGreaterThanOrEqual 仿函數(對這一點甚至是 IsEqual 仿函數)使用默認的 ComparableComparator 這個默認的 Comparator 假定兩個要比較的對象實現了 javalangComparable 接口並對第一個參數(在將它類型轉換為 Comparable 後)只是調用 compareTo 方法傳遞第二個參數作為這個方法的參數
  
  通過將比較工作委派給這個對象本身對於 String 比較(像對 item 目錄所做的)和 Double 比較(像對 item 價格所做的)可以原樣使用默認的 Comparator String 和 Double 都是實現了 Comparable 接口的默認 Java 類型
  
  將二元謂詞適配為一元
  
  可能注意到的第二件事是我引入了一個名為 BinaryPredicateUnaryPredicate 的新仿函數這個仿函數(類似於在 清單 中第一次遇到的 BinaryFunctionUnaryFunction 仿函數)將一個二元謂詞接口適配為一元接口 BinaryPredicateUnaryPredicate 仿函數可以認為是一個帶有一個參數的一元謂詞它在內部用同一個參數的兩個副本計算包裝的二元謂詞
  
  isEligibleForDiscount 對象封裝了一個完整的業務規則如您所見它的構造方式 ── 即通過將構造塊從下到上放到一起以構成更復雜的塊再將它們放到一起以構成更復雜的塊等等 ── 使它本身天然地成為某種可視化的規則構造器最後的規則對象可以是任意復雜的表達式它可以動態地構造然後傳遞以計算底層業務規則
  
  對集合操作
  
  GoF Iterator 模式(請參閱 參考資料) 提供了不公開其底層表示而訪問集合對象的元素的方法這種方法背後的思路是迭代與數據結構不再相關聯 (即它不是集合的一部分)這種方式本身要使用一個表示集合中特定位置的對象並用一個循環條件 (在集合中增加其邏輯位置)以遍歷集合中所有元素循環體中的其他指令可以檢查和/或操作集合中當前 Iterator 對象位置上的元素在本例中我們對迭代沒有什麼控制(例如必須調用多少次 next 每次試圖訪問 next 元素時必須首先檢查超出范圍錯誤) 此外迭代器必須使用與別人一樣的公共接口訪問底層數據結構的成員這使得訪問效率不高這種迭代器常被稱為 外部迭代器(External Iterator)
  
  FP 對這個問題采取了一種非常不同的方式集合類有一個高階函數後者以一個仿函數作為參數並在內部對集合的每一個成員應用它在本例中因為迭代器共享了數據結構的實現所以您可以完成控制迭代此外迭代很快因為它可以直接訪問數據結構成員這種迭代器常被稱為 內部迭代器(internal Iterator)
  
  Apache Functor 庫提供了各種非嚴格地基於 C++ 標准模板庫實現的內部 Iterator 它提供了一個名為 Algorithms 的 實用工具類這個類有一個名為 foreach 的方法 foreach 方法以一個 Iterator 對象和一個一元 Procedure 作為輸入並對遍歷 Iterator 時遇到的每一個元素(元素本身是作為過程的一個參數傳遞的)運行一次
  
  使用內部迭代器
  
  一個簡單的例子將可以說明外部和內部 Iterator 的不同假定提供了一組 SETLItem 對象並要求累積列表中成本高於 美元的那些商品的定價清單 展現了完成這一工作的代碼
  
  清單 使用外部和內部迭代器
  package syssetlfp;
  import javautil*;
  import monsfunctorAlgorithms;
  import monsfunctorUnaryFunction;
  import monsfunctorUnaryProcedure;
  import reConstant;
  import llectionFilteredIterator;
  import paratorIsGreaterThanOrEqual;
  import positeUnaryCompositeBinaryPredicate;
  public class TestE
  {
  public static void main(String[] args)
  {
  Vector items = new Vector();
  for (int i=; i<; i++)
  {
  SETLItem item = new SETLItem();
  if (i%==)
  itemsetPrice();
  else
  itemsetPrice(i);
  itemsadd(item);
  }
  TestE t = new TestE();
  Systemoutprintln(The sum calculated using External Iterator is: +
  tcalcPriceExternalIterator(items));
  Systemoutprintln(The sum calculated using Internal Iterator is: +
  tcalcPriceInternalIterator(items));
  }
  
  public int calcPriceExternalIterator(List items)
  {
  int runningSum = ;
  Iterator i = erator();
  while (ihasNext())
  {
  int itemPrice = ((SETLItem)inext())getPrice();
  if (itemPrice >= )
  runningSum += itemPrice;
  }
  return runningSum;
  }
  
  public int calcPriceInternalIterator(List items)
  {
  Iterator i = erator();
  UnaryFunction getItemPrice =
  new UnaryFunction()
  {
  public Object evaluate (Object obj)
  {
  return new Double(((SETLItem)obj)getPrice());
  }
  };
  Constant usd = new Constant(new Double());
  BinaryPredicateUnaryPredicate moreThanUSD = new BinaryPredicateUnaryPredicate
  (new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual() getItemPrice usd));
  FilteredIterator fi = new FilteredIterator(i moreThanUSD);
  Summer addPrice = new Summer();
  Algorithmsforeach(fi addPrice);
  return addPricegetSum();
  }
  
  static class Summer implements UnaryProcedure
  {
  private int sum=;
  public void run(Object obj)
  {
  sum += ((SETLItem)obj)getPrice();
  }
  public int getSum()
  {
  return sum;
  }
  }
  }
  
  在 main() 方法中設置一個有 種商品的列表其中奇數元素的價格為 美元(在真實應用程序中將使用調用 JDBC 獲得的 ResultSet 而不是本例中使用的 Vector
  
  然後用兩種不同的方法對列表執行所需要的操作 calcPriceExternalIterator 和 calcPriceInternalIterator 正如其名字所表明的前者基於 ExternalIterator 而後者基於 InternalIterator 您將關心後一種方法因為所有 Java 開發人員都應該熟悉前者注意 InternalIterator 方法使用由 Apache Functor 庫提供的兩個結構
  
  第一個結構稱為 FilteredIterator 它取一個迭代器加上一個一元 謂詞 作為輸入並返回一個帶有所感興趣的屬性的 Iterator 這個屬性給出了在遍歷 Iterator 時遇到的每一個滿足在 謂詞 中規定的條件的元素(因此由 FilteredIterator 的一個實例返回的 Iterator 可以作為 FilteredIterator 的第二個實例的輸入傳遞以此類推以設置過濾器鏈用於根據多種標准分步挑出元素)在本例中只對滿足 一元謂詞 大於或等於 美元規則的商品感興趣這種規則是在名為 moreThanUSD 的 BinaryPredicateUnaryPredicate 中規定的我們在 清單 中第一次遇到了它
  
  Apache Functor 庫提供的第二個結構是名為 Algorithms 的實用程序類在 前面描述 過這個類在這個例子中名為 Summer 的一元過程只是包含傳遞給它的 SETLItem 實例的定價並將它添加到(本地)運行的 total 變量上這是一個實現了前面討論的內部迭代器概念的類
  
  使用仿函數進行集合操縱
  
  我討論了用仿函數和高階函數編寫模塊的大量基礎知識我將用最後一個展示如何用仿函數實現集合操縱操作的例子作為結束
  
  通常有兩種描述集合成員關系的方式第一種是完全列出集合中的所有元素這是 Java 編程人員傳統上使用的機制 ── javautilSet 接口提供了一個名為 add(Object) 的方法如果作為參數傳遞到底層集合中的對象還未存在的話該方法就添加它
  
  不過當集合中的元素共享某些公共屬性時通過聲明惟一地標識了集合中元素的屬性可以更高效地描述集合的成員關系例如後一種解決方案適合於集合成員的數量很大以致不能在內存中顯式地維護一個集合實現(像前一種方式那樣)的情況
  
  在這種情況下可以用一個一元 謂詞 表示這個集合顯然一個一元 謂詞 隱式定義了一組可以導致謂詞計算為 true的所有值(對象)事實上所有集合操作都可以用不同類型的謂詞組合來定義清單 中展示了這一點
  
  清單 使用仿函數的集合操作
  
  package syssetlfp;
  import monsfunctorUnaryPredicate;
  import positeUnaryAnd;
  import positeUnaryNot;
  import positeUnaryOr;
  public class SetOps
  {
  public static UnaryPredicate union(UnaryPredicate up UnaryPredicate up)
  {
  return new UnaryOr(up up);
  }
  
  public static UnaryPredicate intersection(UnaryPredicate up UnaryPredicate up)
  {
  return new UnaryAnd(up up);
  }
  
  public static UnaryPredicate difference(UnaryPredicate up UnaryPredicate up)
  {
  return new UnaryAnd(up new UnaryNot(up));
  }
  
  public static UnaryPredicate symmetricDifference(UnaryPredicate up
  UnaryPredicate up)
  {
  return difference(union(up up) intersection(up up));
  }
  }
  
  用一元 謂詞 來描述集合 並集(union) 和 交集(intersection) 操作的定義應當是明確的如果一個對象至少使指示兩個集合的兩個一元 謂詞 中的一個計算為 true那麼這個對象就屬於兩個集合的並集(邏輯 Or )如果它使兩個一元 謂詞 都計算為 true那麼它就屬於兩個集合的交集(邏輯 And )
  
  兩個集合的差在數學上定義為屬於第一個集合但是不屬於第二個集合一組元素根據這個定義靜態方法 difference 也容易理解
  
  最後兩個集合的對稱差(symmetric difference)定義為只屬於兩個集合中的一個(或者兩個都不屬於)的所有元素這可以取兩個集合的並集然後從中刪除屬於兩個集合的交集的元素得到就是說它是對原來的集合(在這裡是一元 謂詞 )使用 union 和 intersection 操作分別得到的兩個集合的 difference 操作後一個定義解釋了為什麼用前三種方法作為第四個方法中的構建塊
  
  結束語
  
  模塊化是任何平台上高生產率和成功的編程的關鍵這一點早已被認識到了Java 開發人員的問題是模塊化編程不僅是將問題分解它還要求能將小的解決方案粘接到一起成為一個有效的整體由於這種類型的開發繼承了函數編程范型在 Java 平台上開發模塊化代碼時使用函數編程技術應是很自然的事
  
  在本文中我介紹了兩種函數編程技術它們可以容易地結合到 Java 開發實踐中正如從這裡看到的閉包和高階函數對於 Java 開發人員來說並不是完全陌生的它們可以有效地結合以創建一些非常有用的模塊化解決方案
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25697.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.