能夠把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())
函數式接口就是隻定義一個抽象方法的接口,例如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個步驟就能夠將行爲抽象出來:設計
具體來看看這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); } } }
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的類型上下文推斷出來的。上下文中須要的類型稱爲目標類型。例如:
filter(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));
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 |
方法引用主要有三類:
能夠利用類的名稱和關鍵字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");