Java8新特性之:Lambda表達式

一. Lambda定義(λ):

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

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

    -- 傳遞,Lambda可以作爲參數傳遞給方法或存儲在變量中:

    -- 簡潔。


二. Lambda表達式結構:

    1. 參數列表;

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

        

        

            


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章