作者:@Ryan-Miao
本文為作者原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處:http://www.cnblogs.com/woshimrf/p/java8-lambda.html


目錄

1.1 Java8中有效的Lambda表達(dá)式
1.2 Lambda的基本語法
2.1 函數(shù)式接口可以做什么
3.1 第1步: 行為參數(shù)化
3.2 第2步:使用函數(shù)式接口來傳遞行為
3.3 第3步: 傳遞Lambda
4.1 Predicate
4.2 Consuer
4.3 Function
4.4 基本類型函數(shù)接口
9.1 原始方案
9.2 使用List內(nèi)置sort
9.3 Lambda表達(dá)式代替匿名內(nèi)部類
9.4 進(jìn)一步優(yōu)化Lambda

豬腳:以下內(nèi)容參考《Java 8 in Action》

本次學(xué)習(xí)內(nèi)容:

  • Lambda 基本模式

  • 環(huán)繞執(zhí)行模式

  • 函數(shù)式接口,類型推斷

  • 方法引用

  • Lambda 復(fù)合

上一篇: Java8學(xué)習(xí)(2)- 通過行為參數(shù)化傳遞代碼--lambda代替策略模式


1. 結(jié)構(gòu)

初始化一個(gè)比較器:

Comparator<Apple> byWeight = new Comparator<Apple>() {    public int copare(Apple a1, Apple a2){        return a1.getWeight().compareTo(a2.getWeight() );
    }
}

使用Lambda表達(dá)式:

Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight() );

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  • 參數(shù)列表--compare方法的的兩個(gè)參數(shù)

  • 箭頭 --- 把參數(shù)列表與lambda主體分割開

  • Lambda主體 --- 表達(dá)式的值就是Lambda的返回值

1.1 Java8中有效的Lambda表達(dá)式

接收一個(gè)字符串,并返回字符串長(zhǎng)度int

(String a) -> s.length()

接收一個(gè)Apple類參數(shù),返回一個(gè)boolean值

(Apple a) -> a.getWeight() > 150

接收兩個(gè)參數(shù),沒有返回值(void),多行語句需要用大括號(hào)包圍

(int x, int y) -> {
    System.out.println("Result:");
    System.out.println(x + y);
}

不接收參數(shù),返回一個(gè)值

()-> 42

接收兩個(gè)參數(shù),返回一個(gè)值

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight() );

1.2 Lambda的基本語法

(parameters) -> expression
或
(parameters) -> {statements}

2. 函數(shù)式接口

在上次的學(xué)習(xí)中的通過行為參數(shù)化傳遞代碼Predicate(T)就是一個(gè)函數(shù)式接口:

public interface Predicate<T> {    boolean test(T t);
}

函數(shù)式接口就是只定義一個(gè)抽象方法的接口。
Java API中很多符合這個(gè)條件。比如:

public interface Comparable<T> {    public int compareTo(T o);
}public interface Runnable {    public abstract void run();
}@FunctionalInterfacepublic interface Callable<V> {    V call() throws Exception;
}

2.1 函數(shù)式接口可以做什么

Lambda表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實(shí)現(xiàn),并把表達(dá)式作為函數(shù)式接口的實(shí)例(函數(shù)式接口一個(gè)具體實(shí)現(xiàn)的實(shí)例)。就像內(nèi)部類一樣,但看起來比內(nèi)部類簡(jiǎn)潔。

Runnable r1 = () -> System.out.println("1");

Runnable r2 = new Runnable(){    public void run(){
        System.out.println("2");
    }
};public static void process(Runnable r) {
    r.run();
}process(r1);process(r2);process(() -> System.out.println(3));

@FunctionalInterface是一個(gè)標(biāo)注,用來告訴編譯器這是一個(gè)函數(shù)式接口,如果不滿足函數(shù)式接口的條件,編譯器就會(huì)報(bào)錯(cuò)。當(dāng)然,這不是必須的。好處是編譯器幫助檢查問題。

3. 一步步修改為L(zhǎng)ambda表達(dá)式

Lambda式提供了傳遞方法的能力。這種能力首先可以用來處理樣板代碼。比如JDBC連接,比如file讀寫。這些操作會(huì)有try-catcha-finally,但我們更關(guān)心的是中間的部分。那么,是不是可以將中間的部分提取出來,當(dāng)做參數(shù)傳遞進(jìn)來?

3.1 第1步: 行為參數(shù)化

下面是讀一行:

public String read(){    try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {        return br.readLine();
    } catch (IOException e) {
        e.printStackTrace();
    }    
    return null;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

行為參數(shù)化就是把一個(gè)過程行為轉(zhuǎn)換成參數(shù)。在這里就是將br.readLine()提取成參數(shù)。

3.2 第2步:使用函數(shù)式接口來傳遞行為

定義一個(gè)接口來執(zhí)行上述的行為:

public interface BufferedReaderProcessor{    String process(BufferedReader b) throws IOException;
}

然后把這個(gè)接口當(dāng)作參數(shù):

public String read(BufferedReaderProcessor p) throws IOException{    try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){        return p.process(br);
    }
}

3.3 第3步: 傳遞Lambda

@Testpublic void readFile() throws IOException {
    String oneLine = read(BufferedReader::readLine);
    String twoLine = read((BufferedReader b) -> b.readLine() + b.readLine());
}

如此,我們就把中間的邏輯抽出來了。把行為抽象成一個(gè)接口調(diào)用,然后通過Lambda來實(shí)現(xiàn)接口的行為。傳遞參數(shù)。完畢。

4. Java API中內(nèi)置的一些函數(shù)式接口

Java API中內(nèi)置了一些很有用的Function接口。

4.1 Predicate

java.util.function.Predicate<T>定義了一個(gè)抽象方法,返回一個(gè)boolean。
使用demo如下:

private <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);
        }
    }    return results;
}@Testpublic void testPredicate(){
    List<String> list = Arrays.asList("aa","bbb","ccc");
    List<String> noEmpty = filter(list, (String s) -> !s.isEmpty());
}

4.2 Consuer

java.util.function.Consumer<T>定義了一個(gè)抽象方法,接收一個(gè)參數(shù)。

private <T> void forEach(List<T> list, Consumer<T> c){    for (T t : list) {
        c.accept(t);
    }
}@Testpublic void testConsumer() {
    List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);    forEach(integers, System.out::println);
}

4.3 Function

java.util.function.Function<T,R>定義了一個(gè)抽象方法,接收一個(gè)參數(shù)T,返回一個(gè)對(duì)象R

private <T,R> List<R> map(List<T> list, Function<T,R> f){
    List<R> result = new ArrayList<>();    for (T t : list) {
        result.add(f.apply(t));
    }    return result;
}@Testpublic void testFunction(){
    List<String> strings = Arrays.asList("a", "bb", "ccc");
    List<Integer> lengths = map(strings, String::length);
}

4.4 基本類型函數(shù)接口

前面三個(gè)泛型函數(shù)式接口Predicate<T>、Consumer<T>、Function<T,R>,這些接口是專門為引用類型設(shè)計(jì)的。那么基本類型怎么辦?我們知道可以自動(dòng)裝箱嘛。但裝箱是有損耗的。裝箱(boxing)的本質(zhì)是把原始類型包裹起來,并保存在堆里。因此裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來獲取包裹的原始值。

Java8為函數(shù)式接口帶來了專門的版本。

@Testpublic void testIntPredicate() {    //無裝箱
    IntPredicate intPredicate = (int t) -> t%2 == 0;    boolean isEven = intPredicate.test(100);
    Assert.assertTrue(isEven);    //裝箱
    Predicate<Integer> integerPredicate = (Integer i) -> i%2 == 0;    boolean isEven2 = integerPredicate.test(100);
    Assert.assertTrue(isEven2);
}

類似的還有:

Java 8中的常用函數(shù)式接口
大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)
大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

5. Lambda原理

  • 編譯器可以推斷出方法的參數(shù)類型,由此可以省略一些樣板代碼。

  • void和其他返回值做了兼容性處理

6. Lambda的局部變量

在Lambda中可以使用局部變量,但要求必須是final的。因?yàn)長(zhǎng)ambda可能在另一個(gè)線程中運(yùn)行,而局部變量是在棧上的,Lambda作為額外的線程會(huì)拷貝一份變量副本。這樣可能會(huì)出現(xiàn)同步問題,因?yàn)橹骶€程的局部變量或許已經(jīng)被回收了?;诖?,必須要求final的。

而實(shí)例變量則沒問題,因?yàn)閷?shí)例變量存儲(chǔ)于堆中,堆是共享的。

7. 方法引用

Lambda表達(dá)式可以用方法引用來表示。比如

(String s) -> s.length()
==
String::length

這是因?yàn)榭梢酝ㄟ^Lambda表達(dá)式的參數(shù)以及方法來確定一個(gè)方法。在這里,每個(gè)方法都叫做方法簽名。方法簽名由方法名+參數(shù)列表唯一確定。其實(shí)就是重載的判斷方式。

當(dāng)Lambda的主體只是一個(gè)簡(jiǎn)單的方法調(diào)用的時(shí)候,我們可以直接使用一個(gè)方法引用來代替。方法引用可以知道要接受的參數(shù)類型,以及方法體的邏輯。

方法引用結(jié)構(gòu):
類名::方法名

什么可以使用方法引用?

  • 靜態(tài)方法。

  • 指向任意類型實(shí)例方法的方法引用。

  • 指向現(xiàn)有對(duì)象的實(shí)例方法。

8. 構(gòu)造函數(shù)引用

構(gòu)造函數(shù)可以通過類名::new的方式引用。

9. Lambda實(shí)戰(zhàn)

目標(biāo): 用不同的排序策略給apple排序。
過程: 把一個(gè)原始粗暴的解決方案變得更加簡(jiǎn)單。
資料: 行為參數(shù)化匿名類,Lambda方法引用.
最終: inventory.sort(comparing(Apple::getWeight) );

9.1 原始方案

/** * Created by ryan on 7/20/17. */public class AppleSort {    private List<Apple> inventory;    @Before
    public void setUp() {
        inventory = new ArrayList<>();
        inventory.add(new Apple("red", 1));
        inventory.add(new Apple("red", 3));
        inventory.add(new Apple("red", 2));
        inventory.add(new Apple("red", 21));
    }    @Test
    public void sort_old() {
        Collections.sort(inventory, new Comparator<Apple>() {            @Override
            public int compare(Apple o1, Apple o2) {                return o1.getWeight() - o2.getWeight();
            }
        });        printApples();
    }    private void printApples() {
        inventory.forEach(System.out::println);
    }
}

排序首先要注意的一點(diǎn)就是排序的標(biāo)準(zhǔn)。那么要搞清楚為什么這樣寫?

Comparator定義的其實(shí)就是一個(gè)方法,此處就是將排序的原則抽取出來。特別符合Lambda的思想!這里先不說Lambda,先說這個(gè)方法的作用:定義什么時(shí)候發(fā)生交換
跟蹤源碼可以發(fā)現(xiàn)這樣一段代碼:

//java.util.Arrays#mergeSort(java.lang.Object[], java.lang.Object[], int, int, int, java.util.Comparator)if (length < INSERTIONSORT_THRESHOLD) {    for (int i=low; i<high; i++)        for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)            swap(dest, j, j-1);    return;
}

假設(shè)比較的兩個(gè)數(shù)為o1o2,并且o1o2前一位(left>right)。如下:

....o1,o2...

compare(o1,o2)的結(jié)果大于0則,o1o2交換。那么,顯然,如果

compare(o1,o2) = o1-o2

則說明,前一個(gè)值比后一個(gè)值大的時(shí)候,發(fā)生交換。也即大的往后冒泡。就是升序了。
所以:

  • o1-o2 升序

  • o2-o1 降序

9.2 使用List內(nèi)置sort

好消息是Java8提供了sort方法給list:java.util.List#sort:
則原始方案轉(zhuǎn)換為:

@Testpublic void sort1(){
    inventory.sort(new Comparator<Apple>() {        @Override
        public int compare(Apple o1, Apple o2) {            return o1.getWeight() - o2.getWeight();
        }
    });    printApples();
}

9.3 Lambda表達(dá)式代替匿名內(nèi)部類

從之前的學(xué)習(xí)可以得到,幾乎所有的匿名內(nèi)部類都可以用Lambda表達(dá)式替代!

inventory.sort((o1, o2) -> o1.getWeight() - o2.getWeight());

9.4 進(jìn)一步優(yōu)化Lambda

Comparator提供了一個(gè)生成Comparator的方法:

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

其中,Function<T,R>已經(jīng)在前面學(xué)習(xí)過了,就是一個(gè)接受一個(gè)參數(shù)并返回另一個(gè)參數(shù)的函數(shù)式接口。在本例中,apple.getWeight()符合接受一個(gè)參數(shù)apple返回一個(gè)int。那么,就可以使用這個(gè)方法:

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

進(jìn)一步,將Lambda改為方法引用:

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

這里有個(gè)問題,記得之前講的基本類型的自動(dòng)裝箱嗎。Apple::getWeight的返回值是int。而comparing的返回值是一個(gè)對(duì)象。那么,必然要經(jīng)過自動(dòng)裝箱的過程。所以,應(yīng)該使用基本類型的函數(shù)式接口:

inventory.sort(Comparator.comparingInt(Apple::getWeight));

至此,基本已經(jīng)改造完畢了。最多就是靜態(tài)引入comparingInt方法:

inventory.sort(comparingInt(Apple::getWeight));

目標(biāo)達(dá)到。相比原始方法,不要太簡(jiǎn)潔!

話說,這種是不是只能默認(rèn)升序?因此沒有任何一個(gè)單詞可以看出排序規(guī)則。

是的,想要降序?

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

So do it,and change it,no regret!

http://www.cnblogs.com/woshimrf/p/java8-lambda.html