《java 8 實戰》讀書筆記 -第三章 Lambda表達式

第三章 Lambda表達式

函數式接口

函數式接口就是隻定義一個抽象方法的接口,哪怕有不少默認方法,只要接口只定義了一個抽象方法,它就仍然是一個函數式接口。

經常使用函數式接口

圖片描述
圖片描述

函數描述符

函數式接口的抽象方法的簽名稱爲函數描述符。

在哪裏可使用Lambda?

只有在須要函數式接口的時候才能夠傳遞Lambda
下哪些是使用Lambda表達式的有效方式?
(1)java

execute(() -> {});
public void execute(Runnable r){ 
r.run(); 
}

(2)app

return () -> "Tricky example ;-)";

(3)ide

Predicate<Apple> p = (Apple a) -> a.getWeight();

答案:只有1和2是有效的
第一個例子有效,是由於Lambda() -> {}具備簽名() -> void,這和Runnable中的
抽象方法run的簽名相匹配。請注意,此代碼運行後什麼都不會作,由於Lambda是空的!
第二個例子也是有效的。事實上,fetch方法的返回類型是Callable<String>。
Callable<String>基本上就定義了一個方法,簽名是() -> String,其中T被String代替
了。由於Lambda() -> "Trickyexample;-)"的簽名是() -> String,因此在這個上下文
中可使用Lambda。
第三個例子無效,由於Lambda表達式(Apple a) -> a.getWeight()的簽名是(Apple) ->
Integer,這和Predicate<Apple>:(Apple) -> boolean中定義的test方法的簽名不一樣。函數

@FunctionalInterface又是怎麼回事

這個標註用於表示該接口會設計成一個函數式接口,@FunctionalInterface不是必需的,它就像是@Override標註表示方法被重寫了。

Java 7中的帶資源的try語句

它已經簡化了代碼,由於你不須要顯式地關閉資源了.fetch

public static String processFile() throws IOException { 
  try (BufferedReader br = 
  new BufferedReader(new FileReader("data.txt"))) { 
  return br.readLine(); 
  } 
}

函數式接口:Predicate斷言

java.util.function.Predicate<T>接口定義了一個名叫test的抽象方法,它接受泛型T對象,並返回一個boolean。

函數式接口:Consumer

java.util.function.Consumer<T>定義了一個名叫accept的抽象方法,它接受泛型T的對象,沒有返回(void)。

函數式接口:Function

java.util.function.Function<T, R>接口定義了一個叫做apply的方法,它接受一個泛型T的對象,並返回一個泛型R的對象。
eg:ui

@FunctionalInterface 
public interface Function<T, R>{ 
 R apply(T t); 
} 
public static <T, R> List<R> map(List<T> list, 
 Function<T, R> f) { 
 List<R> result = new ArrayList<>(); 
 for(T s: list){ 
 result.add(f.apply(s)); 
 } 
 return result; 
} 
// [7, 2, 6] 
List<Integer> l = map( 
 Arrays.asList("lambdas","in","action"), 
 (String s) -> s.length() 
 );

避免自動裝箱、拆箱

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

關於異常

請注意,任何庫中的函數式接口都不容許拋出受檢異常(checked exception)。若是你須要Lambda表達式來拋出異常,有兩種辦法:定義一個本身的函數式接口,並聲明受檢異常,或者把Lambda包在一個try/catch塊中。

目標類型

Lambda表達式須要的類型稱爲目標類型。(即對應的函數式接口)

類型推斷

你還能夠進一步簡化你的代碼。Java編譯器會從上下文(目標類型)推斷出用什麼函數式接
口來配合Lambda表達式,這意味着它也能夠推斷出適合Lambda的簽名,由於函數描述符能夠通
過目標類型來獲得。這樣作的好處在於,編譯器能夠了解Lambda表達式的參數類型,這樣就可
以在Lambda語法中省去標註參數類型。換句話說,Java編譯器會像下面這樣推斷Lambda的參數
類型:spa

List<Apple> greenApples = 
filter(inventory, a -> "green".equals(a.getColor()));

方法引用

爲三種不一樣類型的Lambda表達式構建方法引用的辦法:
圖片描述
List<String> str = Arrays.asList("a","b","A","B"); 
str.sort((s1, s2) -> s1.compareToIgnoreCase(s2));

Lambda表達式的簽名與Comparator的函數描述符兼容。利用前面所述的方法,這個例子可
以用方法引用改寫成下面的樣子:設計

List<String> str = Arrays.asList("a","b","A","B"); 
str.sort(String::compareToIgnoreCase);

構造函數引用

對於一個現有構造函數,你能夠利用它的名稱和關鍵字new來建立它的一個引用:
ClassName::new。

構造函數引用
要怎麼樣才能對具備三個參數的構造函數,好比Color(int, int, int),使用構造函數引用呢?
答案:你看,構造函數引用的語法是ClassName::new,那麼在這個例子裏面就是Color::new。可是你須要與構造函數引用的簽名匹配的函數式接口。可是語言自己並無提供這樣的函數式接口,你能夠本身建立一個:code

public interface TriFunction<T, U, V, R>{ 
 R apply(T t, U u, V v); 
}

如今你能夠像下面這樣使用構造函數引用了:對象

TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new;

Comparator 類內部comparing實現

comparing 方法一
查看 Comparator 類內部實現,還有一個 comparing 方法,實現以下,

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
           Function<? super T, ? extends U> keyExtractor)
   {
       Objects.requireNonNull(keyExtractor);
       return (Comparator<T> & Serializable)
           (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
   }

其返回值是 (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); 一個lambda表達式,也就是一個Compator
eg:

Comparator<Apple> c = Comparator.comparing(Apple::getWeight);

comparing 方法二

public static <T, U> Comparator<T> comparing(
           Function<? super T, ? extends U> keyExtractor,
           Comparator<? super U> keyComparator)
   {
       Objects.requireNonNull(keyExtractor);
       Objects.requireNonNull(keyComparator);
       return (Comparator<T> & Serializable)
           (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                             keyExtractor.apply(c2));
   }

和comparing 方法一不一樣的是 該方法多了一個參數 keyComparator ,keyComparator 是建立一個自定義的比較器。

Collections.sort(employees, Comparator.comparing(
               Employee::getName, (s1, s2) -> {
                   return s2.compareTo(s1);
               }));

比較器複合

逆序

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

比較器鏈
thenComparing方法就是作這個用的

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

謂詞複合(斷言複合)

謂詞接口包括三個方法:negate、and和or,讓你能夠重用已有的Predicate來建立更復
雜的謂詞。好比,你可使用negate方法來返回一個Predicate的非,好比蘋果不是紅的:

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

你可能想要把兩個Lambda用and方法組合起來,好比一個蘋果既是紅色又比較重:

Predicate<Apple> redAndHeavyApple = 
redApple.and(a -> a.getWeight() > 150);

你能夠進一步組合謂詞,表達要麼是重(150克以上)的紅蘋果,要麼是綠蘋果:

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

這一點爲何很好呢?從簡單Lambda表達式出發,你能夠構建更復雜的表達式,但讀起來仍然和問題的陳述差很少!請注意,and和or方法是按照在表達式鏈中的位置,從左向右肯定優先級的。所以,a.or(b).and(c)能夠看做(a || b) && c。

函數複合

你還能夠把Function接口所表明的Lambda表達式複合起來。Function接口爲此配了andThen和compose兩個默認方法,它們都會返回Function的一個實例。

  • andThen方法會返回一個函數,它先對輸入應用一個給定函數,再對輸出應用另外一個函數;用compose方法,先把給定的函數用做compose的參>數裏面給的那個函數,而後再把函數自己用於結果。