Java中的Lambda表達式入門

瞭解如何在Java程序中使用lambda表達式和函數式編程技術

Java中的Lambda表達式入門

在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建立。設計模式

Lambdas:入門

甲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將源代碼提供給一個小型應用程序,讓您可使用此示例。

清單1. LambdaDemo.java(版本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。

清單2. LambdaDemo.java(版本2)

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

編譯此源代碼後,運行該應用程序。您將發現與先前顯示的輸出相同的輸出。

Lambdas和Streams API

除了簡化源代碼外,lambda在Java的面向功能的Streams API中也起着重要做用。它們描述了傳遞給各類API方法的功能單元。

深刻Java Lambda

爲了有效地使用lambda,您必須瞭解lambda表達式的語法以及目標類型的概念。您還須要瞭解lambda如何與範圍,局部變量,thisand和super關鍵字以及異常進行交互。我將在如下各節中介紹全部這些主題。

lambda如何實現

Lambda是根據Java虛擬機的invokedynamic指令和java.lang.invokeAPI來實現的。觀看視頻「 Lambda:深刻了解」以瞭解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

Lambdas和var

從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並不是語句。可是,在基於語句的lambda主體中,每一個語句必須以分號結尾。

清單3提供了一個簡單的應用程序,該應用程序演示了lambda語法;請注意,此清單創建在前兩個代碼示例的基礎上。

清單3. LambdaDemo.java(版本3)

@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提供了一個演示這些目標類型上下文的應用程序。

清單4. LambdaDemo.java(版本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乾貨知識,請關注我~

謝謝~

相關文章
相關標籤/搜索