行為參數化和Lambda表達式


  行為參數化是指拿出一個代碼塊把他准備好卻不執行它。這個代碼塊以后可以被程序的其他部分調用,意味着你可以推遲這塊代碼的執行。方法接受多種行為作為參數,並在內部使用來完成不同的行為。行為參數話的好處在於可以把迭代要篩選的集合的邏輯與對集合中的每個元素應用的行為區分開來。

  Java的匿名類可以同時聲明和實力化一個類。但它往往很笨重,占用了很多空間同時還不易理解。

  可以把Lambda表達式看作匿名功能,它基本上是沒有聲明名稱的方法,但和匿名類一樣,它也可以作為參數傳遞給一個方法。Lambda表達式可以簡潔地表示可傳遞的匿名(Lambda不像普通方法那樣有一個明確的名稱)函數(Lambda函數不屬於某個特定的類,但Lambda有參數列表,函數主題,返回類型和異常列表)的一種方式:它沒有名稱,但它有參數列表,返回類型還可能有一個可以拋出的異常列表。Lambda表達式可以作為參數傳遞給方法或存儲在變量中,並且無需像匿名類那樣寫很多模板代碼。

  Lambda表達式有三個部分:參數列表,箭頭和Lambda主體。(parameters) -> expression 或 (parameters) -> {statements;}

  通常在函數式接口上使用Lambda表達式。函數式接口就是只定義一個抽象方法的接口。

  Lambda表達式以內聯的形式為函數式接口的抽象方法提供實現,並把整個表達式作為函數式接口的實例,具體來講時函數式接口的一個具體實現的實例。函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名,這種抽象方法稱為函數描述符。()->void代表了參數列表為空且返回void的函數。函數式接口通常帶有@FunctionalInterface的注解。它用於表示該接口會被設計為一個函數式接口。若定義了一個不是函數式接口但帶有@FunctionalInterface的注解,編譯器將報錯:Multiple non-overriding abstract methods found in interface XX。

  

  java.util.function包中引入了幾個新的函數式接口:

  Predicate<T>接口定義了一個名為test的抽象方法,它接受一個T對象,並返回一個boolean。通常用於一個涉及類型T的布爾表達式。

  @FunctionalInterface

  public interface Predicate<T>{

    boolean test(T t);

  }

  public static <T> List<T> filter(List<T> list, Predicate<T> p){

    List<T> results = new ArrayList<>();

    for(T t : list){

      if(p.test(t)){

        results.add(t);

      }

    }

  }

 

  Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();

  List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

 

  Consumer<T>定義了一個accpet抽象方法,它接受一個T對象,沒有返回。通常用於訪問類型T的對象並對其執行某些操作。

  @FunctionalInterface

  public interface Consumer<T>{

    void accept(T t);

  }

  public static <T> void forEach(List<T> list, Consumer<T> c){

    for(T t : list){

      c.accpet(t);

    } 

  }

  forEach(Arrays.asList(1, 2, 3, 4), (Integer i) -> System.out.println());

 

  Function<T, R>接口定義一個apply方法,接受一個T對象,並返回一個R對象。通常用於將輸入對象的信息映射到輸出。

  @FunctionalInterface

  public interfaces Function<T, R>{

    R apply(T t);

  }

  public static <T, R> List<R> map(List<T> list, Function<T, R> f){

    List<R> results = new ArrayList<>();

    for(T t : list){

      results.add(f.apply(t));

    }

    return results;

  }

  List<Integer> list = map(Arrays.asList("lambdas", "in", "action"), (String s) -> s.length());

 

  將一個原始類型轉換為對應的引用類型的機制稱為裝箱。將引用類型轉換為對應的原始類型稱為拆箱。Java有自動裝箱機制,但是會影響性能。裝箱后的值本質上是把原始類型包裹起來,並保存在堆里。因此裝箱后的值需要更多的內存,並需要額外的內存搜索來獲取被包裹的原始值。

  通常,針對專門的輸入參數類型的函數式接口的名稱要加上對應的原始類型前綴,比如DoublePredicate,IntConsumer等。Function接口還有針對輸出參數類型的變種:ToIntFunction<T>, IntToDoubleFunction等。

  

                      java8常用函數式接口

  函數式接口        函數描述符      原始類型特化

  Predicate<T>         T -> boolean      IntPredicate,LongPredicate,DoublePredicate

  Consumer<T>      T -> void       IntConsumer, LongConsumer, DoubleConsumer

  Function<T, R>       T -> R          IntFunction<R>, IntToDoubleFunction, IntToLongFunction,LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T>

  Supplier<T>         () -> T         BooleanSupplier, IntSupplier, LongSuppier, DoubleSupplier

  UnaryOperator<T>     T -> T                     IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator

  BinaryOperator<T>    (T, T) -> T        IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator

  BiPredicate<L, R>    (L, R) -> boolean  

  BiConsumer<T, U>   (T, U) -> void     ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>

  BiFunction<T, U, R>   (T, U) -> R     ToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U>

  

  任何函數式接口都不允許拋出受檢查異常。若需要Lambda表達式來拋出異常,有兩種方l法,一種是定義一個自己的函數式接口,並聲明受檢查異常,另一種是把Lambda放到一個try-catch塊中。

  Lambda的類型是從使用Lambda的上下文推斷出來的。上下文中Lambda表達式需要的類型稱為目標類型。若Lambda的主體是語句表達式,它就和一個返回void的函數描述符兼容。

  Lambda可以使用自有變量,它們被稱為捕獲Lambda。Lambda可以沒有限制地捕獲實例變量和靜態變量。但局部變量必須顯式的聲明為final或事實上就是final。因為Lambda表達式只能捕獲指派給它們的局部變量一次,實例變量可以看作捕獲最終局部變量this。

  閉包是一個函數的實例,且它可以無限制地訪問哪個函數的非本地變量。

 

  方法引用可以被看作僅僅調用特定方法的Lambda的一種快捷寫法。它的基本思想是若一個Lambda代表的只是直接調用這個方法,最好還是用名稱來調用它,而不是去描述如何調用它。方法的引用實際上就是根據已有的方法實現來創建Lambda表達式。使用方法引用時,目標引用放在分隔符::前,方法的名稱放在后面。方法的名稱不需要括號,因為並么有實際調用這個方法。

  方法引用主要有三類

    1.指向靜態方法的方法引用,如Integer::parseInt

    2.指向任意類型實例方法的方法引用,如String::length

    3.指向現有對象的實例方法的引用,如obja::methodName

  對於構造函數,利用它的名字和關鍵字new來創建它的一個引用。ClassName::new。若是無參的構造函數,() -> ClassName更適合。  

  Comparator具有一個叫comparing的靜態輔助方法,它接受一個Function來提取Comparable健值,並聲稱一個Comparator對象。

  inventory.sort(comparing( (a) -> a.getWeight()));

 

  把多個簡單的Lambda復合成復雜的表達式。如兩個謂詞之間做個or操作或讓另一個函數的結果變為另一個函數的輸出。

  可以通過reversed間給定的比較器逆序。inventory.sort(comparing((a) -> a.getWeight()).reversed());

  thenComparing接受一個函數作為參數,若兩個對象用第一個Comparator比較后是一樣的,則提供第二Comparator。

  inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));

  

  謂語接口包括三個方法:negate,and和or。

  negate方法用來返回一個Predicate的非。

  Predicate<Apple> notRedApple = redApple.negate();

  and和or可以將兩個Lambda表達式組合起來。and和or方法是按照在表達式鏈中的位置,從左到右確定優先級的

  Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150).or(a -> "green".equals(a.getColor()));

  

  Function中有andThen和compose兩個默認方法,他們都會返回一個Function實例。

  andThen方法會返回一個函數,它先對輸入應用過一個給定函數,在對輸出應用另一個函數。

  Function<Integer, Integer> f = x -> x + 1;

  Function<Integer, Integer> g = x -> x * 2;

  Function<Integer, Integer> h = f.andThen(g);

  int result = h.apply(1); // g(f(x))

  h = f.compose(g);

  result = h.apply(1); // f(g(x));


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM