lambda表達式是一種沒有名字的函數,它擁有函數體和參數。java
lambda表達式的語法十分簡單:參數->主體。經過->來分離參數和主體。程序員
lambda表達式能夠有零個參數,一個參數或多個參數,參數能夠指定類型,在編譯器能夠推導出參數類型的狀況下,也能夠省略參數類型。 數組
兩個參數的例子:app
(String first, String second)-> Integer.compare(first.length(), second.length())
0個參數的例子:ide
() -> { for (int i = 0; i < 1000; i++) doWork(); }
關於省略參數類型,能夠參考泛型省略類型來理解。從jdk7開始,泛型能夠簡化寫成以下形式:函數
Map<String, String> myMap = new HashMap<>();
編譯器會根據變量聲明時的泛型類型自動推斷出實例化HashMap時的泛型類型。this
一樣的,若是編譯器能夠推導出Lambda表達式中參數的類型,也能夠將其省略,例如:spa
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
上例lambda建立了一個函數式接口Comparator的對象(後文將介紹函數式接口),編譯器根據聲明,能夠推斷出first和second的類型爲String。此時,參數類型可省略。在只有一個參數,且可推斷出其類型的狀況下,能夠再將括號省略: 線程
EventHandler<ActionEvent> listener = event ->System.out.println("Thanks for clicking!");
同方法參數同樣,表達式參數也能夠添加annotations或者final修飾:code
(final String name) -> ... (@NonNull String name) ->
lambda表達式的主體必定要有返回值。
若是主體只有一句,則能夠省略大括號:
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
多於一句的狀況,須要用{}括上:
(String first, String second) -> {
if (first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}
主體必須有返回值,只在某些分支上有返回值也是不合法的,例如:
(int x) -> { if (x >= 0) return 1; }
這個例子是不合法的。
只包含一個抽象方法的接口叫作函數式接口。
函數式接口可以使用註解@FunctionalInterface標註(不強制,可是若是標註了,編譯器就會檢查它是否只包含一個抽象方法)
能夠經過lambda表達式建立函數式接口的對象,這是lambda表達式在java中作的最重要的事情
在jdk8之前,其實已經存在着一些接口,符合上述函數式接口的定義。
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
在jdk8之前,這些接口的使用方式與其餘接口並沒有不一樣。
經過兩個例子來講明lambda表達式如何建立函數式接口實例
1.建立Runnable函數式接口實例,以啓動線程——jdk8之前:
import java.util.*; public class OldStyle { public static void main(String[] args) { // 啓動一個線程
Worker w = new Worker(); new Thread(w).start(); // 啓動一個線程
new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }).start(); } } class Worker implements Runnable { public void run() { System.out.println(Thread.currentThread().getName()); } }
運行結果:
Thread-0
Thread-1
從代碼角度來看,無論是經過內部類仍是經過匿名內部類,啓動線程須要編寫的代碼都較爲繁瑣,其中,由程序員自定義的僅僅是run方法中的這一句話:
System.out.println(Thread.currentThread().getName());
lambda表達式風格的啓動線程:
// 啓動一個線程 Runnable runner = () -> System.out.println(Thread.currentThread().getName()); runner.run();
第一行實際上建立了一個函數式接口Runnable的實例runner,能夠看出,lambda表達式的實體,剛好是run方法的方法體部分。
2.建立Comparator函數式接口實例,實現根據String的長度來排序一個String數組——jdk8之前:
import java.util.*; public class OldStyle { public static void main(String[] args) { // 排序一個數組
class LengthComparator implements Comparator<String> { public int compare(String first, String second) { return Integer.compare(first.length(), second.length()); } } String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, new LengthComparator()); System.out.println(Arrays.toString(strings)); } }
lambda表達式:
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一個數組
String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, (first, second) -> Integer.compare(first.length(), second.length())); System.out.println(Arrays.toString(strings)); } }
能夠看出,函數式接口經過lambda表達式建立實例,是如此的精簡
jdk8的java.util.function包下,又
定義了一些函數式接口以及針對基本數據類型的子接口。
Predicate -- 傳入一個參數,返回一個bool結果, 方法爲boolean test(T t) Consumer -- 傳入一個參數,無返回值,純消費。 方法爲void accept(T t) Function<t,r> -- 傳入一個參數,返回一個結果,方法爲R apply(T t) Supplier -- 無參數傳入,返回一個結果,方法爲T get() UnaryOperator -- 一元操做符, 繼承Function<t,t>,傳入參數的類型和返回類型相同。 BinaryOperator -- 二元操做符, 傳入的兩個參數的類型和返回類型相同, 繼承BiFunction
方法引用加強了lambda表達式的可讀性
方法表達式的三種主要狀況:
方法引用將會執行該類(對象)的指定靜態(實例)方法。
方法引用例1:根據字母順序(不區分大小寫)排序一個字符串數組:
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一個數組 String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, (s1, s2) -> { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; }); System.out.println(Arrays.toString(strings)); } }
上述例子,因爲lambda表達式的主體代碼較長,致使代碼可讀性降低,經過方法引用能夠解決這個問題
方法引用例2:類::靜態方法
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一個數組 String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, LambdaStyle::myCompareToIgnoreCase); System.out.println(Arrays.toString(strings)); } public static int myCompareToIgnoreCase(String s1, String s2){ int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } }
將主體代碼抽出來寫到一個方法中,而後引用這個方法。
方法引用例3:對象::實例方法
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一個數組 String[] strings = "Mary had a little lamb".split(" "); LambdaStyle lambdaStyle = new LambdaStyle(); Arrays.sort(strings, lambdaStyle::myCompareToIgnoreCase); System.out.println(Arrays.toString(strings)); } public int myCompareToIgnoreCase(String s1, String s2){ int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } }
對類::實例方法這種狀況的方法引用來講,第一個參數會成爲執行方法的對象。
經過一個例子來講明。在String類中實際上已經提供了不區分大小寫比較字符串的方法:
public int compareToIgnoreCase(String str)
這個方法的用法爲:
String s = "jdfjsjfjskd"; String ss = "dskfksdkf"; int i = s.compareToIgnoreCase(ss);
System.out.println(i);
方法引用例4:類::實例
import java.util.*; public class LambdaStyle { public static void main(String[] args) { // 排序一個數組 String[] strings = "Mary had a little lamb".split(" "); Arrays.sort(strings, String::compareToIgnoreCase); System.out.println(Arrays.toString(strings)); } }
分析例4,對於函數式接口Comparator來講,它的抽象方法爲:
int compare(T o1, T o2);
這個方法有兩個參數,對於例1來講,出如今lambda表達式參數中的s1,s2,實際上就是這兩個參數。例2,例3中的方法myCompareToIgnoreCase的參數也是如此。
而對於第三個關於方法引用的例子,String的compareToIgnoreCase方法只有一個參數。這時,第一個參數將會做爲執行方法的對象,(s1.compareToIgnoreCase(s2))
另外,也能夠經過以下形式方法引用:
this::實例方法
super::實例方法
方法引用例5:
public class SuperTest { public static void main(String[] args) { class Greeter { public void greet() { System.out.println("Hello, world!"); } } class ConcurrentGreeter extends Greeter { public void greet() { Thread t = new Thread(super::greet); t.start(); } } new ConcurrentGreeter().greet(); } }
和方法引用類似,只不過經過以下方式引用:
類::new
構造器引用能夠生成一個類的實例
例1
Stream<Button> stream = labels.stream().map(Button::new); Button[] buttons4 = stream.toArray(Button[]::new);
lambda表達式引用值,而不是變量。
lambda表達式中引用的局部變量必須是:顯示聲明爲final的,或者雖然沒有被聲明爲final,但實際上也算是有效的final的。
在Java中與其類似的是匿名內部類關於局部變量的引用。
例1:匿名內部類引用局部變量——jdk8之前
public class Outter { public static void main(String[] args) { final String s1 = "Hello "; new Inner() { @Override public void printName(String name) { System.out.println(s1 + name); } }.printName("Lucy"); } } interface Inner{ public void printName(String name); };
如例1所示,在jdk8之前,匿名內部類引用外部類定義的局部變量,則該變量必須是final的。
jdk8將這個條件放寬,匿名內部類也能夠訪問外部類有效的final局部變量——即這個變量雖然沒有顯示聲明爲final,但定義後也沒有再發生變化。
例2:匿名內部類引用局部變量——jdk8
public class Outter { public static void main(String[] args) { String s1 = "Hello "; new Inner() { @Override public void printName(String name) { System.out.println(s1 + name); } }.printName("Lucy"); } } interface Inner{ public void printName(String name); };
匿名內部類引用的外部類變量s1能夠不顯示定義爲final。可是s1必須在初始化後再也不改變。
lambda表達式對於引用局部變量的規則同jdk8中的匿名內部類同樣:顯示聲明爲final的,或者雖然沒有被聲明爲final,但實際上也算是有效的final的
import java.io.*; import java.nio.charset.*; import java.nio.file.*; import java.util.*; import java.util.stream.*; public class VariableScope { public static void main(String[] args) { repeatMessage("Hello", 100); } public static void repeatMessage(String text, int count) { Runnable r = () -> { for (int i = 0; i < count; i++) { System.out.println(text); Thread.yield(); } }; new Thread(r).start(); } public static void repeatMessage2(String text, int count) { Runnable r = () -> { while (count > 0) { // count--; // Error: Can't mutate captured variable System.out.println(text); Thread.yield(); } }; new Thread(r).start(); } public static void countMatches(Path dir, String word) throws IOException { Path[] files = getDescendants(dir); int matches = 0; for (Path p : files) new Thread(() -> { if (contains(p, word)) { // matches++; // ERROR: Illegal to mutate matches } }).start(); } private static int matches; public static void countMatches2(Path dir, String word) { Path[] files = getDescendants(dir); for (Path p : files) new Thread(() -> { if (contains(p, word)) { matches++; // CAUTION: Legal to mutate matches, but not threadsafe } }).start(); } // Warning: Bad code ahead public static List<Path> collectMatches(Path dir, String word) { Path[] files = getDescendants(dir); List<Path> matches = new ArrayList<>(); for (Path p : files) new Thread(() -> { if (contains(p, word)) { matches.add(p); // CAUTION: Legal to mutate matches, but not threadsafe } }).start(); return matches; } public static Path[] getDescendants(Path dir) { try { try (Stream<Path> entries = Files.walk(dir)) { return entries.toArray(Path[]::new); } } catch (IOException ex) { return new Path[0]; } } public static boolean contains(Path p, String word) { try { return new String(Files.readAllBytes(p), StandardCharsets.UTF_8).contains(word); } catch (IOException ex) { return false; } } }
-----
-----
---
、