Java8實戰 — Lambda表達式詳解

Lambda管中窺豹

    能夠把Lambda表達式理解爲簡潔地表示可傳遞的匿名函數的一種方式:它沒有名稱,但它有參數、函數主體和返回類型,可能還有一個能夠拋出的異常列表。java

    何爲更簡介,作一個例子,使用匿名類和Lambda定義一個Comparator對象:數組

package cn.net.bysoft.chapter3;

import java.util.Comparator;

public class Example1 {

    public static void main(String[] args) {
        // 使用匿名類定義Comparator對象
        Comparator<Apple> byWeight = new Comparator<Apple>() {
            @Override
            public int compare(Apple a1, Apple a2) {
                return a1.getWidth().compareTo(a2.getWidth());
            }
        };
        // 使用Lambda定義Comparator對象
        // 以 -> 爲界限,-> 左側部分是方法參數, -> 右側部分是方法主體
        Comparator<Apple> byWeight_lambda = (Apple a1, Apple a2) -> a1.getWidth().compareTo(a2.getWidth());
    }

}

    在進一步,下面給出了Java8中五個有效的Lambda表達式:app

// 具備一個String類型的參數並返回一個int,沒有return語句,由於已經隱含了return
        (String s) -> s.length()
        // 具備一個Apple類型的參數並返回一個boolean
        (Apple a) -> a.getWeight() > 150
        // 具備兩個int類型的參數而沒有返回值
        (int x, int y) -> {
            System.out.println(x + y);
        }
        // 沒有參數並返回一個int
        () -> 42
        // 有兩個Apple類型的參數並返回一個int
        (Apple a1, Apple a2) -> a1.getWeight().comparaTo(a2.getWeight())

在哪裏以及如何使用Lambda

    函數式接口就是隻定義一個抽象方法的接口,例如Comparator和Runnable。ide

    Lambda表達式容許你之內聯的形式爲函數式接口的抽象方法提供實現,並把整個表達式做爲函數式接口的實例。用匿名類也能夠完成一樣的事情,但比較笨拙。函數

    讓咱們經過一個例子來看看在哪裏以及如何使用Lambda表達式。測試

    打開一個資源,作一些處理,關閉資源:spa

package cn.net.bysoft.chapter3;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Example2 {
    public static void main(String[] args) {
        try {
            String s = processFile();
            System.out.println(s);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // 打開一個資源,讀取一行,關閉資源
    // 使用了Java7中的帶資源的try語句,不須要顯示關閉資源
    public static String processFile() throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) {
            return br.readLine();
        }
    }
}

    上面這段代碼是有侷限性的,只能讀文件的第一行。若是想要返回前兩行,甚至更多該怎麼辦?.net

    通常來講,經過4個步驟就能夠將行爲抽象出來:設計

  1. 使用函數式接口;
  2. 行爲參數化;
  3. 執行一個行爲;
  4. 傳遞Lambda表達式;

    具體來看看這4步,首先,建立一個函數式接口:code

package cn.net.bysoft.chapter3;

import java.io.BufferedReader;
import java.io.IOException;

@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader br) throws IOException;
}

    接下來,行爲參數化:

public static String processFile(BufferedReaderProcessor p) throws IOException {
    ...
}

    在來,執行行爲:

public static String processFile(BufferedReaderProcessor p) throws IOException {
    try(BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) {
        return p.process(br);
    }
}

    最後,傳遞lambda:

String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());

    測試:

package cn.net.bysoft.chapter3;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Example2 {
    public static void main(String[] args) {
        // 1.行爲參數化
        try {
            // 4.傳遞Lambda
            String oneLine = processFile((BufferedReader br) -> br.readLine());
            System.out.println(oneLine);
            String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());
            System.out.println(twoLine);
            
            /**
             * output: 
             * aaa
             * aaabbb
             * */
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // 2.傳遞行爲
    public static String processFile(BufferedReaderProcessor p) throws IOException {
        // 3.調用行爲
        try(BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) {
            return p.process(br);
        }
    }
}

使用Java8中的函數式接口

    Java API中已經有了一些函數式接口,好比Comparable、Runnable和Callable等。

    Java8的設計師幫你在java.util.function包中引入了幾個新的函數式接口:

函數式接口 函數描述符
Predicate<T> T -> boolean
Consumer<T> T -> void
Function<T,R> T -> R
Supplier<T> () -> T
UnaryOperator<T> (T) -> T
BinaryOperator<T> (T,T) -> T
BiPredicate<L,R> (L,R) -> boolean
BiConsumer<T,U> (T,U) -> void
BiFunction<T,U,R> (T,U) -> R

    寫個例子使用幾個經常使用的函數式接口:

package cn.net.bysoft.chapter3;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class Example3 {
    public static void main(String[] args) {
        
        // 使用Predicate<T>
        Apples apples = new Apples();

        List<Apple> green_apples = filter(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));

        // 使用Consumer<T>
        System.out.println("綠蘋果有:");
        forEach(green_apples, (Apple apple) -> System.out.println(apple.getId()));

        // 使用Function<T>
        // 將字符串的長度放到一個List<Integer>中
        System.out.println("數組中的字符長度:");
        List<Integer> list = map(Arrays.asList("lambdas", "in", "action"), (String s) -> s.length());
        forEach(list, (Integer i) -> System.out.println(i));
        
    }

    private static <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> result = new ArrayList<>();
        for (T t : list)
            if (p.test(t))
                result.add(t);
        return result;
    }

    private static <T> void forEach(List<T> list, Consumer<T> c) {
        for (T t : list)
            c.accept(t);
    }

    private static <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;
    }
}

Lambda的類型檢查、類型推斷以及限制

類型檢查

    Lambda的類型檢查是從使用Lambda的類型上下文推斷出來的。上下文中須要的類型稱爲目標類型。例如:

filter(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));
  1. 檢查filter的方法聲明;
  2. 發現目標類型是Predicate<Apple>
  3. 發現Predicate<Apple>是一個函數式接口,定義了一個test()方法;
  4. test方法描述了一個函數描述符,接收一個Apple,返回一個boolean;
  5. filter的任何實際參數都必須匹配這個要求;

類型推斷

    Java編譯器會從上下文推斷出用什麼函數式接口來配合Lambda表達式,這意味着它也能夠推斷出適用Lambda的簽名,由於函數描述符能夠經過目標類型來獲得。這樣作的好處在於,編譯器能夠了解Lambda表達式的參數類型,這樣就能夠在Lambda語法中省去標註參數類型,例如:

Comparator<Apple> byWeight_lambda = (a1, a2) -> a1.getWidth().compareTo(a2.getWidth());

    有時候顯示寫出類型更容易讀,有時候去掉他們更容易讀。

使用局部變量

    Lambda容許使用自由變量,它們被稱做捕獲Lambda,例如:

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
r.run();

    Lambda能夠沒有限制地捕獲實例變量和敬愛變量。但局部變量必須顯示聲明爲final,或事實上是final。例如,下面這段代碼就沒法編譯:

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);  // error
portNumber = 1327;
r.run();

方法引用

簡單描述

    方法引用讓你能夠重複使用現有的方法,並像Lambda同樣傳遞它們,例如:

inventory.sort(comparing(Apple::getWidth));
List<String> str = Arrays.asList("a", "b", "A", "B");
str.sort(String::compareToIgnoreCase);
for(String s : str) System.out.println(s);

    能夠把方法引用看做針對僅僅涉及單一方法的語法糖,由於表達一樣的事情時要寫的代碼更少了,例如:

(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

    方法引用主要有三類:

  1. 指向靜態方法的方法引用,Integer的parseInt寫做Integer::parseInt;
  2. 指向任意類型實力方法的引用,String的length寫做String::length;
  3. 指向現有對象的實例方法的引用,局部變量evn的實例方法getValue寫做evn::getValue;

 構造函數引用

    能夠利用類的名稱和關鍵字new來建立一個類,寫做ClassName::new。

    加入一個構造函數沒有參數,它適合Supplier的簽名,() -> Apple,能夠這樣作:

Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();

    若是有帶參數的構造函數,則可使用BiFunction:

BiFunction<Integer, String, Apple> c2 = Apple::new;
Apple a2 = c2.apply(1, "greed");
相關文章
相關標籤/搜索