在Java SE 8以前,一般使用匿名類將功能傳遞給方法。這種作法混淆了源代碼,使其難以理解。Java 8經過引入lambda消除了這個問題。本教程首先介紹lambda語言功能,而後提供有關使用lambda表達式以及目標類型進行函數編程的更詳細的介紹。您還將學習lambda如何與範圍,局部變量,thisand super關鍵字以及Java異常交互。java
請注意,本教程中的代碼示例與JDK 12兼容。express
在本教程中,我不會介紹您之前沒有學過的任何非lambda語言功能,可是我將經過本系列以前沒有討論過的類型來演示lambda。一個例子是java.lang.Math類。我將在之後的Java 101教程中介紹這些類型。如今,我建議閱讀JDK 12 API文檔以瞭解有關它們的更多信息。編程
下載獲取代碼下載本教程中示例應用程序的源代碼。由Jeff Friesen爲JavaWorld建立。設計模式
甲lambda表達式(拉姆達)描述了一個代碼塊(一匿名功能能夠傳遞到構造或方法用於後續執行)。構造函數或方法接收lambda做爲參數。考慮如下示例:數組
[ 在這個由12部分組成的綜合課程中,從入門概念到高級設計模式學習Java!]ide
() -> System.out.println("Hello")
此示例標識用於將消息輸出到標準輸出流的lambda。從左到右,()標識lambda的形式參數列表(示例中沒有參數),->表示該表達式是lambda,而且System.out.println("Hello")是要執行的代碼。函數式編程
Lambda簡化了功能性接口的使用,它們是帶註釋的接口,每一個接口都精確地聲明一個抽象方法(儘管它們也能夠聲明默認,靜態和私有方法的任意組合)。例如,標準類庫提供java.lang.Runnable具備單個抽象void run()方法的接口。該功能接口的聲明以下所示:函數
@FunctionalInterfacepublic interface Runnable{ public abstract void run();}
類庫註釋Runnable與@FunctionalInterface,這是的一個實例
java.lang.FunctionalInterface註釋類型。FunctionalInterface用於註釋在lambda上下文中使用的那些接口。學習
Lambda沒有明確的接口類型。相反,編譯器使用周圍的上下文來推斷指定lambda時要實例化的功能接口-lambda 綁定到該接口。例如,假設我指定了如下代碼片斷,它將前一個lambda做爲參數傳遞給java.lang.Thread類的Thread(Runnable target)構造函數:ui
new Thread(() -> System.out.println("Hello"));
編譯器肯定將lambda傳遞給它,Thread(Runnable r)由於這是惟一知足lambda的構造Runnable函數:是一個函數接口,lambda的空形式參數列表()與的空參數列表匹配run(),而且返回類型(void)也一致。Lambda綁定到Runnable。
清單1將源代碼提供給一個小型應用程序,讓您可使用此示例。
public class LambdaDemo{ public static void main(String[] args) { new Thread(() -> System.out.println("Hello")).start(); }}
編譯清單1(javac LambdaDemo.java)並運行應用程序(java LambdaDemo)。您應該觀察如下輸出:
Hello
Lambda能夠大大簡化您必須編寫的源代碼數量,而且還可使源代碼更易於理解。例如,若是沒有lambda,您可能會指定清單2的更詳細的代碼,該代碼基於實現的匿名類的實例Runnable。
public class LambdaDemo{ public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; new Thread(r).start(); }}
編譯此源代碼後,運行該應用程序。您將發現與先前顯示的輸出相同的輸出。
除了簡化源代碼外,lambda在Java的面向功能的Streams API中也起着重要做用。它們描述了傳遞給各類API方法的功能單元。
爲了有效地使用lambda,您必須瞭解lambda表達式的語法以及目標類型的概念。您還須要瞭解lambda如何與範圍,局部變量,thisand和super關鍵字以及異常進行交互。我將在如下各節中介紹全部這些主題。
Lambda是根據Java虛擬機的invokedynamic指令和java.lang.invokeAPI來實現的。觀看視頻「 Lambda:深刻了解」以瞭解Lambda體系結構。
每一個lambda都遵循如下語法:
( formal-parameter-list ) -> { expression-or-statements }
的formal-parameter-list是逗號分隔的形式參數,它必須在運行時功能接口的一個抽象方法的參數相匹配的列表。若是省略它們的類型,則編譯器將從使用lambda的上下文中推斷出這些類型。考慮如下示例:
(double a, double b) // types explicitly specified(a, b) // types inferred by compiler
從Java SE 11開始,您能夠將類型名稱替換爲var。例如,您能夠指定(var a, var b)。
您必須爲多個或沒有形式參數指定括號。可是,在指定單個形式參數時,能夠省略括號(儘管沒必要這樣作)。(這僅適用於參數名稱-當還指定類型時,必須使用括號。)請考慮如下其餘示例:
x // parentheses omitted due to single formal parameter(double x) // parentheses required because type is also present() // parentheses required when no formal parameters(x, y) // parentheses required because of multiple formal parameters
的formal-parameter-list後面是->令牌,其後是expression-or-statements-表達式或語句塊(稱爲lambda的主體)。與基於表達式的主體不一樣,必須將基於語句的主體置於open({)和close(})大括號字符之間:
(double radius) -> Math.PI * radius * radiusradius -> { return Math.PI * radius * radius; }radius -> { System.out.println(radius); return Math.PI * radius * radius; }
第一個示例的基於表達式的lambda主體沒必要放在括號之間。第二個示例將基於表達式的主體轉換爲基於語句的主體,return必須在其中指定以返回表達式的值。最後的示例演示了多個語句,沒有括號就沒法表達。
請注意;,在前面的示例中不存在分號()。在每種狀況下,lambda主體都不會以分號終止,由於lambda並不是語句。可是,在基於語句的lambda主體中,每一個語句必須以分號結尾。
清單3提供了一個簡單的應用程序,該應用程序演示了lambda語法;請注意,此清單創建在前兩個代碼示例的基礎上。
@FunctionalInterfaceinterface BinaryCalculator{ double calculate(double value1, double value2);}@FunctionalInterfaceinterface UnaryCalculator{ double calculate(double value);}public class LambdaDemo{ public static void main(String[] args) { System.out.printf("18 + 36.5 = %f%n", calculate((double v1, double v2) -> v1 + v2, 18, 36.5)); System.out.printf("89 / 2.9 = %f%n", calculate((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf("-89 = %f%n", calculate(v -> -v, 89)); System.out.printf("18 * 18 = %f%n", calculate((double v) -> v * v, 18)); } static double calculate(BinaryCalculator calc, double v1, double v2) { return calc.calculate(v1, v2); } static double calculate(UnaryCalculator calc, double v) { return calc.calculate(v); }}
清單3首先介紹BinaryCalculator和UnaryCalculator接口,它們的calculate()方法分別對兩個輸入參數或單個輸入參數執行計算。此清單還引入了一個LambdaDemo類,該類的main()方法演示了這些功能接口。
功能接口在static double calculate(BinaryCalculator calc, double v1, double v2)和static double calculate(UnaryCalculator calc, double v)方法中進行了演示。Lambda將代碼做爲數據傳遞給這些方法,這些方法做爲BinaryCalculator或UnaryCalculator實例接收。
編譯清單3並運行該應用程序。您應該觀察如下輸出:
18 + 36.5 = 54.50000089 / 2.9 = 30.689655-89 = -89.00000018 * 18 = 324.000000
Lambda與隱式目標類型關聯,該目標類型標識Lambda綁定到的對象的類型。目標類型必須是從上下文推斷出的功能接口,它將lambda限制爲出如今如下上下文中:
清單4提供了一個演示這些目標類型上下文的應用程序。
import java.io.File;import java.io.FileFilter;import java.nio.file.Files;import java.nio.file.FileSystem;import java.nio.file.FileSystems;import java.nio.file.FileVisitor;import java.nio.file.FileVisitResult;import java.nio.file.Path;import java.nio.file.PathMatcher;import java.nio.file.Paths;import java.nio.file.SimpleFileVisitor;import java.nio.file.attribute.BasicFileAttributes;import java.security.AccessController;import java.security.PrivilegedAction;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.List;import java.util.concurrent.Callable;public class LambdaDemo{ public static void main(String[] args) throws Exception { // Target type #1: variable declaration Runnable r = () -> { System.out.println("running"); }; r.run(); // Target type #2: assignment r = () -> System.out.println("running"); r.run(); // Target type #3: return statement (in getFilter()) File[] files = new File(".").listFiles(getFilter("txt")); for (int i = 0; i < files.length; i++) System.out.println(files[i]); // Target type #4: array initializer FileSystem fs = FileSystems.getDefault(); final PathMatcher matchers[] = { (path) -> path.toString().endsWith("txt"), (path) -> path.toString().endsWith("java") }; FileVisitor<Path> visitor; visitor = new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { Path name = file.getFileName(); for (int i = 0; i < matchers.length; i++) { if (matchers[i].matches(name)) System.out.printf("Found matched file: '%s'.%n", file); } return FileVisitResult.CONTINUE; } }; Files.walkFileTree(Paths.get("."), visitor); // Target type #5: method or constructor arguments new Thread(() -> System.out.println("running")).start(); // Target type #6: lambda body (a nested lambda) Callable<Runnable> callable = () -> () -> System.out.println("called"); callable.call().run(); // Target type #7: ternary conditional expression boolean ascendingSort = false; Comparator<String> cmp; cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2) : (s1, s2) -> s2.compareTo(s1); List<String> cities = Arrays.asList("Washington", "London", "Rome", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moscow"); Collections.sort(cities, cmp); for (int i = 0; i < cities.size(); i++) System.out.println(cities.get(i)); // Target type #8: cast expression String user = AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("user.name")); System.out.println(user); } static FileFilter getFilter(String ext) { return (pathname) -> pathname.toString().endsWith(ext); }}
若哪裏有錯誤或您有更好的方法,請留言指出~
若要獲取更多Java乾貨知識,請關注我~
謝謝~