Java8新特性之:Lambda表達式

一. Lambda定義(λ):java

    -- 匿名,它不像普通方法那樣有一個明確的名稱;express

    -- 函數,它不像普通方法那樣屬於某個特定的類,但和方法同樣,Lambda有參數列表、函數主體、返回類型或拋出異常列表:app

    -- 傳遞,Lambda能夠做爲參數傳遞給方法或存儲在變量中:ide

    -- 簡潔。函數


二. Lambda表達式結構:性能

    1. 參數列表;spa

    2. 箭頭:箭頭->把參數列表與Lambda主體分隔開;線程

    3. Lambda主體:表達式就是Lambda表達式的例子。對象


三.Lambda基本語法:排序

    (parameters) -> expression

    或

    (parameters) -> { statements; }

    使用顯式返回語句時須要使用花括號「{}」。

        eg:  Lambda示例:

使用案例 Lambda示例
無參數,返回void () -> {}
無參數,返回String () -> "Raoul"
無參數,返回String(利用顯式返回語句) () -> { return "Result";}
布爾表達式 (List<String> list) -> list.isEmpty()
建立對象 () -> new Apple(10);
消費一個對象 (Apple apple) -> { System.out.println(a.getWeight()); }
從一個對象中選擇/抽取 (String s) -> s.length()
組合兩個值 (int a, int b) -> a * b
比較兩個對象 (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())


四. 在哪裏使用Lambda

    能夠在函數式接口上使用Lambda表達式。

    函數式接口:只定義一個抽象方法的接口。

    @FunctionalInterface:代表爲函數式接口,但它不是必須的。

    (T, U) -> R表達式展現了應當如何思考函數描述符。左側表明了參數類型。這裏它表明一個函數,具備兩個參數,分別爲泛型T和U,返回類型爲R。

    Java的泛型只能綁定到引用類型,當須要引用原始類型時由於自動裝箱和拆箱機制時,裝箱後的值須要更多的內存,須要付出性能代價,爲了不裝箱操做對Predicate<T>和Function<T, R>等通用函數式接口的原始類型特化:IntPredicate、IntToLongFunction等。

        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,

LongSupplier,

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

ObjectIntConsumer<T>,

ObjectLongConsumer<T>,

ObjectDoubleConsumer<T>

BiFunction<T, U, R> (T, U) -> R

ToIntBiFunction<T, U>,

ToLongBiFunction<T, U>,

ToDoubleBiFunction<T, U>

Runnable () -> void

    當須要Lambda表達式拋出異常時,有兩種方式:

        -- 本身編寫新的函數式接口,並聲明受檢異常(任何函數式接口都不容許拋出受檢異常);

        -- 將Lambda包在一個try/catch塊中。

@FunctionalInterface
public interface BufferedReaderProcessor {
  String process(BufferedReader b) throws IOException;
}
Function<BufferedReader, String> f = (BufferedReader b) -> {
  try {   
    return b.readLine();
  } catch (IOException e) {
    throw new RuntimeException(e);
  }
};


        當Lambda表達式拋出一個異常時,throws語句也必須與Lambda所指類型相匹配。

    若是Lambda的主體是一個語句表達式,它就和一個返回void的函數描述符兼容(固然須要參數列表也兼容)。

        eg:儘管List的add方法返回的是boolean,但如下兩行都是合法的:  

//Predicate返回了一個boolean
Predicate<String> p = s -> list.add(s);

//Consumer返回了一個void
Consumer<String> b = s -> list.add(s);


    Lambda類型推斷:Java編譯器會從上下文(目標類型)中推斷出用什麼函數式接口來配合Lambda表達式,因此也能推斷出適合Lambda的簽名,由於函數描述符能夠經過目標類型來獲得。這樣就能夠在Lambda中省去標註參數類型,當參數只有一個時還能夠省去參數的括號。

    Lambda使用局部變量的限制:Lambda表達式對值封閉,而不是對變量封閉。Lambda表達式引用的局部變量必須是final的,只能有一次賦值。

        這是由於實例變量是儲存在堆中,而局部變量是儲存在棧上。若是Lambda能夠直接訪問局部變量,並且Lambda是在一個線程中使用的,則使用Lambda的線程可能會在分配該變量的線程將這個變量收回以後,去訪問該變量。所以,Java在訪問自由局部變量時,其實是在訪問他的副本而不是訪問原始變量。

    Lambda方法引用:

        方法引用就是Lambda的快捷寫法。目標引用放在分隔符::前面,方法的名稱放在後面。

        eg:

Lambda表達式 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println

        如何構建方法引用:

            主要有三類:

                -- 指向靜態方法的放方法引用;

                -- 指向任意類型實例方法的方法引用;

                -- 指向現有對象的實例方法的方法引用。

    Lambda構造函數引用:

        對於一個現有構造函數,能夠利用它的名稱和關鍵字new來建立它的一個引用:ClassName::new。它的功能與靜態方法的引用相似。

Lambda表達式 等價構造方法引用Lambda表達式

Supplier<Apple> c1 = Apple::new;

Apple a1 = c1.get();

Supplier<Apple> c1 = () -> new Apple();

Apple a1 = c1.get();

Function<Integer, Apple> c2 = Apple::new;

Apple a2 = c2.apply(110);

Function<Integer, Apple> c2 = (weight) -> new Apple(weight);

Apple a2 = c2.apply(110);

BiFunction<String, Integer, Apple> c2 = Apple::new;

Apple a2 = c2.apply("green", 110);

BiFunction<String, Integer, Apple> c2 = (color, weight) -> new Apple(color, weight);

Apple a2 = c2.apply("green", 110);

    

    複合Lambda表達式的有用方法:

複合類型 方法 說明 舉例
比較器複合 reversed() 逆序

//按重量遞減排序

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

.reversed();

thenComparing 比較器鏈(接受一個函數做爲參數,若是兩個對象用第一個Comparator比較以後是同樣的,就提供第二個Comparator

//兩個蘋果同樣重時按國家排序

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

.reversed().

thenComparing(Apple::getCountry);

謂詞複合

(and和or方法的優先級是按照在表達式鏈中的位置,從左向右肯定的)

negate

//產生現有對象redApple的非

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

and 將兩個Lambda用and組合起來

//一個蘋果既是紅色又比較重(連接兩個謂詞來生成一個Predicate對象)

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

or 要麼

//要麼重(150g以上)的紅蘋果,要麼是綠蘋果

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

                                                               .or(a -> "green".equals(a.getColor()));

函數複合 andThen 先對輸入應用一個給定函數,再對輸出應用另外一個函數

//等同於數學上的g(f(x)),返回4

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);

compose 先把給定的函數用做compose的參數裏面給的那個函數,而後再把函數自己用於結果

//等同於數學上的f(g(x)),返回3

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

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

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

int result = h.apply(1);

相關文章
相關標籤/搜索