中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
在 Eclipse IDE 中試用 Lambda 表達(dá)式

在 Eclipse IDE 中試用 Lambda 表達(dá)式

作者:Deepak Vohra

學(xué)習(xí)如何充分利用 lambda 和虛擬擴(kuò)展方法。

2013 年 8 月發(fā)布

Lambda 表達(dá)式也稱為閉包,是匿名類的簡(jiǎn)短形式。Lambda 表達(dá)式簡(jiǎn)化了單一抽象方法聲明接口的使用,因此 lambda 表達(dá)式也稱為功能接口。在 Java SE 7 中,單一方法接口可使用下列選項(xiàng)之一實(shí)現(xiàn)。

  • 創(chuàng)建接口實(shí)現(xiàn)類。
  • 創(chuàng)建匿名類。

可以使用 lambda 表達(dá)式實(shí)現(xiàn)功能接口,無(wú)需創(chuàng)建類或匿名類。Lambda 表達(dá)式只能用于單一方法聲明接口。

Lambda 表達(dá)式旨在支持多核處理器架構(gòu),這種架構(gòu)依賴于提供并行機(jī)制的軟件,而該機(jī)制可以提高性能、減少完成時(shí)間。

Lambda 表達(dá)式具有以下優(yōu)點(diǎn):

  • 簡(jiǎn)明的語(yǔ)法
  • 方法引用和構(gòu)造函數(shù)引用
  • 相比于匿名類,減少了運(yùn)行時(shí)開(kāi)銷

前提條件

要跟隨本文中的示例,請(qǐng)下載并安裝以下軟件:

Lambda 表達(dá)式的語(yǔ)法

Lambda 表達(dá)式的語(yǔ)法如下所示。

(formal parameter list) ->{ expression or statements }

參數(shù)列表是一個(gè)逗號(hào)分隔的形式參數(shù)列表,這些參數(shù)與功能接口中單一方法的形式參數(shù)相對(duì)應(yīng)。指定參數(shù)類型是可選項(xiàng);如果未指定參數(shù)類型,將從上下文推斷。

參數(shù)列表必須用括號(hào)括起來(lái),但當(dāng)指定的單一參數(shù)不帶參數(shù)類型時(shí)除外;指定單一形式參數(shù)時(shí)可以不帶括號(hào)。如果功能接口方法不指定任何形式參數(shù),則必須指定空括號(hào)。

參數(shù)列表后面是 -> 運(yùn)算符,然后是 lambda 主體,即單一表達(dá)式或語(yǔ)句塊。Lambda 主體的結(jié)果必須是下列值之一:

  • void,如果功能接口方法的結(jié)果是 void
  • Java 類型、基元類型或引用類型,與功能接口方法的返回類型相同

Lambda 主體根據(jù)以下選項(xiàng)之一返回結(jié)果:

  • 如果 lambda 主體是單一表達(dá)式,則返回表達(dá)式的值。
  • 如果該方法具有返回類型,且 lambda 主體不是單一表達(dá)式,則 lambda 主體必須使用 return 語(yǔ)句返回值。
  • 如果功能接口方法的結(jié)果是 void,可以提供一個(gè) return 語(yǔ)句,但這不是必需的。

語(yǔ)句塊必須包含在大括號(hào) ({}) 內(nèi),除非語(yǔ)句塊是一個(gè)方法調(diào)用語(yǔ)句,而其調(diào)用的方法的結(jié)果是 void。Lambda 主體的結(jié)果必須與功能接口中單一方法的結(jié)果相同。例如,如果功能接口方法的結(jié)果是 void,則 lambda 表達(dá)式主體不能返回值。如果功能接口方法具有返回類型 String,則 lambda 表達(dá)式主體必須返回 String。如果 lambda 主體是一條語(yǔ)句,并且該方法具有一個(gè)返回類型,則該語(yǔ)句必須是 return 語(yǔ)句。調(diào)用 lambda 表達(dá)式時(shí),將運(yùn)行 lambda 主體中的代碼。

功能接口

Lambda 表達(dá)式與功能接口一起使用,功能接口實(shí)際上是一種只有一個(gè)抽象方法的接口;功能接口可以包含一個(gè)同時(shí)也存在于 Object 類中的方法。功能接口的示例有 java.util.concurrent.Callable(具有單一方法 call())和 java.lang.Runnable(具有單一方法 run())。

區(qū)別在于,匿名接口類需要指定一個(gè)實(shí)例創(chuàng)建表達(dá)式,以便接口和編譯器用來(lái)創(chuàng)建接口實(shí)現(xiàn)類的實(shí)例。與指定接口類型(或類類型)的匿名類不同,lambda 表達(dá)式不指定接口類型。從上下文推斷為其調(diào)用 lambda 表達(dá)式的功能接口,也稱為 lambda 表達(dá)式的目標(biāo)類型

Lambda 表達(dá)式的目標(biāo)類型

Lambda 表達(dá)式有一個(gè)隱式的目標(biāo)類型與之關(guān)聯(lián),因?yàn)槲疵鞔_指定接口類型。在 lambda 表達(dá)式中,lambda 轉(zhuǎn)換的目標(biāo)類型必須是一個(gè)功能接口。從上下文推斷目標(biāo)類型。因此,lambda 表達(dá)式只能用在可以推斷目標(biāo)類型的上下文中。此類上下文包括

  • 變量聲明
  • 賦值
  • return 語(yǔ)句
  • 數(shù)組初始值設(shè)定項(xiàng)
  • 方法或構(gòu)造函數(shù)的參數(shù)
  • Lambda 表達(dá)式主體
  • 三元條件表達(dá)式
  • 轉(zhuǎn)換表達(dá)式

使用支持 Java SE 8 的 Eclipse IDE

要在 Eclipse IDE 中使用 Java 8,您需要下載一個(gè)支持 JDK 8 的 Eclipse 版本。

  1. 在 Eclipse 中,選擇 Windows > Preferences,然后選擇 Java > Installed JREs。使用在前提條件部分下載的 JDK 8 安裝適用于 JDK 8 的 JRE。
  2. 選擇 Java > Compiler,然后將 Compiler compliance level 設(shè)為 1.8,如圖 1 所示。


    圖 1

  3. 單擊 Apply,然后單擊 OK
  4. 在 Eclipse 中創(chuàng)建一個(gè) Java 項(xiàng)目時(shí),請(qǐng)選擇 JDK 1.8 JRE。

接下來(lái),我們將通過(guò)一些示例討論如何使用 lambda 表達(dá)式。

用 Lambda 表達(dá)式創(chuàng)建 Hello 應(yīng)用程序

我們都很熟悉 Hello 應(yīng)用程序,當(dāng)我們提供一個(gè)姓名時(shí),它會(huì)輸出一條消息。Hello 類聲明了兩個(gè)字段、兩個(gè)構(gòu)造函數(shù)和一個(gè) hello() 方法來(lái)輸出消息,如下所示。

public class Hello {   String firstname;   String lastname;   public Hello() {}   public Hello(String firstname, String lastname) {      this.firstname = firstname;      this.lastname = lastname;}   public void hello() {      System.out.println("Hello " + firstname + " " + lastname);}   public static void main(String[] args) {      Hello hello = new Hello(args[0], args[1]);      hello.hello();   }}

現(xiàn)在,我們來(lái)看看 lambda 表達(dá)式如何簡(jiǎn)化 Hello 示例中的語(yǔ)法。首先,我們需要?jiǎng)?chuàng)建一個(gè)功能接口,該接口包含一個(gè)返回“Hello”消息的方法。

interface HelloService {String hello(String firstname, String lastname);    }

創(chuàng)建一個(gè) lambda 表達(dá)式,它包含兩個(gè)參數(shù),與接口方法的參數(shù)相匹配。在 lambda 表達(dá)式的主體中,使用 return 語(yǔ)句創(chuàng)建并返回根據(jù) firstnamelastname 構(gòu)造的“Hello”消息。返回值的類型必須與接口方法的返回類型相同,并且 lambda 表達(dá)式的目標(biāo)必須是功能接口 HelloService。參見(jiàn)清單 1。

public class Hello {   interface HelloService {      String hello(String firstname, String lastname);   }   public static void main(String[] args) {      HelloService helloService=(String firstname, String lastname) -> { String hello="Hello " + firstname + " " + lastname; return hello; };System.out.println(helloService.hello(args[0], args[1]));            }}

清單 1

我們需要先為 hello() 的方法參數(shù)提供一些程序參數(shù),然后才能運(yùn)行 Hello 應(yīng)用程序。在 Package Explorer 中右鍵單擊 Hello.java,然后選擇 Run As > Run Configurations。在 Run Configurations 中,選擇 Arguments 選項(xiàng)卡,在 Program arguments 字段中指定參數(shù),然后單擊 Apply。然后單擊 Close。

要運(yùn)行 Hello.java 應(yīng)用程序,在 Package Explorer 中右鍵單擊 Hello.java,然后選擇 Run As > Java Application。應(yīng)用程序的輸出將顯示在 Eclipse 控制臺(tái)中,如圖 2 所示。


圖 2

Lambda 表達(dá)式中的局部變量

Lambda 表達(dá)式不會(huì)定義新的作用域;lambda 表達(dá)式的作用域與封閉作用域相同。例如,如果 Lambda 主體聲明的局部變量與封閉作用域內(nèi)的變量重名,將產(chǎn)生編譯器錯(cuò)誤 Lambda expression's local variable i cannot re-declare another local variable defined in an enclosing scope,如圖 3 所示。


圖 3

局部變量無(wú)論是在 lambda 表達(dá)式主體中聲明,還是在封閉作用域中聲明,使用之前都必須先初始化。要證明這一點(diǎn),請(qǐng)?jiān)诜忾]方法中聲明一個(gè)局部變量:

int i;

在 lambda 表達(dá)式中使用該局部變量。將產(chǎn)生編譯器錯(cuò)誤 The local variable i may not have been initialized,如圖 4 所示。


圖 4

lambda 表達(dá)式中使用的變量必須處于終態(tài)或等效終態(tài)。要證明這一點(diǎn),請(qǐng)聲明并初始化局部變量:

int i=5;

給 lambda 表達(dá)式主體中的變量賦值。將產(chǎn)生編譯器錯(cuò)誤 Variable i is required to be final or effectively final,如圖 5 所示。


圖 5

可按如下方式將變量 i 聲明為終態(tài)。

final int i=5;

否則,該變量必須為等效終態(tài),即不能在 lambda 表達(dá)式中對(duì)該變量賦值。封閉上下文中的方法參數(shù)變量和異常參數(shù)變量也必須處于終態(tài)或等效終態(tài)。

Lambda 主體中的 thissuper 引用與封閉上下文中一樣,因?yàn)?lambda 表達(dá)式不會(huì)引入新的作用域,這與匿名類不同。

Lambda 表達(dá)式是一種匿名方法

Lambda 表達(dá)式實(shí)際上是一種匿名方法實(shí)現(xiàn);指定形式參數(shù),并使用 return 語(yǔ)句返回值。匿名方法必須按照以下規(guī)則所規(guī)定的與其實(shí)現(xiàn)的功能接口方法兼容。

  • Lambda 表達(dá)式返回的結(jié)果必須與功能接口方法的結(jié)果兼容。如果結(jié)果是 void,則 lambda 主體必須與 void 兼容。如果返回一個(gè)值,則 lambda 主體必須與值兼容。返回值的類型可以是功能接口方法聲明中返回類型的子類型。
  • Lambda 表達(dá)式簽名必須與功能接口方法的簽名相同。Lambda 表達(dá)式簽名不能是功能接口方法簽名的子簽名。
  • Lambda 表達(dá)式只能拋出那些在功能接口方法的 throws 子句中聲明了異常類型或異常超類型的異常。

要證明如果功能接口方法返回一個(gè)結(jié)果,lambda 表達(dá)式也必須返回一個(gè)結(jié)果,請(qǐng)?jiān)谀繕?biāo)類型為 HelloService 的 lambda 表達(dá)式中注釋掉 return 語(yǔ)句。因?yàn)楣δ芙涌?HelloService 中的 hello() 方法具有一個(gè) String 返回類型,因此產(chǎn)生編譯器錯(cuò)誤,如圖 6 所示。


圖 6

如果功能接口方法將結(jié)果聲明為 void,而 lambda 表達(dá)式返回了一個(gè)值,那么將產(chǎn)生編譯器錯(cuò)誤,如圖 7 所示。


圖 7

如果 lambda 表達(dá)式簽名與功能接口方法簽名不完全相同,將產(chǎn)生編譯器錯(cuò)誤。要證明這一點(diǎn),請(qǐng)使 lambda 表達(dá)式參數(shù)列表為空,同時(shí)功能接口方法聲明兩個(gè)形式參數(shù)。將產(chǎn)生編譯器錯(cuò)誤 Lambda expression's signature does not match the signature of the functional interface method,如圖 8 所示。


圖 8

可變參數(shù)與數(shù)組參數(shù)之間不作區(qū)分。例如,功能接口方法按如下方式聲明數(shù)組類型參數(shù):

interface Int {      void setInt(int[] i);   }  

Lambda 表達(dá)式的參數(shù)列表可以聲明可變參數(shù):

Int int1  =(int... i)->{};

異常處理

Lambda 表達(dá)式主體拋出的異常不能超出功能接口方法的 throws 子句中指定的異常數(shù)。如果 lambda 表達(dá)式主體拋出異常,功能接口方法的 throws 子句必須聲明相同的異常類型或其超類型。

要證明這一點(diǎn),在 HelloService 接口的 hello 方法中不聲明 throws 子句,從 lambda 表達(dá)式主體拋出異常。將產(chǎn)生編譯器錯(cuò)誤 Unhandled exception type Exception,如圖 9 所示。


圖 9

如果在功能接口方法中添加與所拋出異常相同的異常類型,編譯器錯(cuò)誤將得以解決,如圖 10 所示。但如果使用以 lambda 表達(dá)式結(jié)果賦值的引用變量來(lái)調(diào)用 hello 方法,將產(chǎn)生編譯器錯(cuò)誤,因?yàn)?main 方法中未對(duì)異常進(jìn)行處理,如圖 10 所示。


圖 10

Lambda 表達(dá)式是一種多態(tài)表達(dá)式

Lambda 表達(dá)式的類型是從目標(biāo)類型推導(dǎo)出來(lái)的類型。相同的 lambda 表達(dá)式在不同的上下文中可以有不同的類型。此類表達(dá)式稱為多態(tài)表達(dá)式。要證明這一點(diǎn),請(qǐng)定義與 HelloService 具有相同抽象方法簽名的另一功能接口,例如:

interface HelloService2 {		String hello(String firstname, String lastname);	}

例如,相同的 lambda 表達(dá)式(下面的表達(dá)式)可用于所聲明的方法具有相同簽名、返回類型以及 throws 子句的兩個(gè)功能接口:

(String firstname, String lastname) -> {         String hello = "Hello " + firstname + " " + lastname;         return hello;      }

沒(méi)有上下文,前面的 lambda 表達(dá)式?jīng)]有類型,因?yàn)樗鼪](méi)有目標(biāo)類型。但是,如果在具有目標(biāo)類型的上下文中使用,則 lambda 表達(dá)式可以根據(jù)目標(biāo)類型具有不同的類型。在以下兩種情況下,前面的 lambda 表達(dá)式具有不同的類型,因?yàn)槟繕?biāo)類型不同:HelloServiceHelloService2

HelloService helloService =(String firstname, String lastname) -> {         String hello = "Hello " + firstname + " " + lastname;         return hello;      };

HelloService2 helloService2 =(String firstname, String lastname) -> {         String hello = "Hello " + firstname + " " + lastname;         return hello;      };

不支持泛型 Lambda。Lambda 表達(dá)式不能引入類型變量。

GUI 應(yīng)用程序中的 Lambda 表達(dá)式

java.awt 軟件包中的 GUI 組件使用 java.awt.event.ActionListener 接口注冊(cè)組件的操作事件。java.awt.event.ActionListener 接口是一個(gè)只有一個(gè)方法的功能接口:actionPerformed(ActionEvent e)。

使用 addActionListener(ActionListener l) 方法將 java.awt.event.ActionListener 注冊(cè)到組件。例如,可以使用應(yīng)用程序中的匿名內(nèi)部類按如下方式將 java.awt.event.ActionListener 注冊(cè)到 java.awt.Button 組件,用以計(jì)算 Button 對(duì)象(稱為 b)被單擊的次數(shù)。(更多詳細(xì)信息,請(qǐng)參見(jiàn)“如何編寫操作監(jiān)聽(tīng)程序”)

b.addActionListener (new ActionListener() {          int numClicks = 0;          public void actionPerformed(ActionEvent e) {             numClicks++;                text.setText("Button Clicked " + numClicks + " times");          }       });

可以用 Lambda 表達(dá)式代替匿名內(nèi)部類,使語(yǔ)法更加簡(jiǎn)潔。以下是使用 lambda 表達(dá)式將 ActionListener 注冊(cè)到 ActionListener 組件的一個(gè)示例:

b.addActionListener(e -> {         numClicks++;         text.setText("Button Clicked " + numClicks + " times");      });   }

對(duì)于只有一個(gè)參數(shù)的 lambda 表達(dá)式,用于指定參數(shù)的括號(hào)可以省略。Lambda 表達(dá)式的目標(biāo)類型(功能接口 ActionListener)是從上下文(即一個(gè)方法調(diào)用)推斷出來(lái)的。

將 lambda 表達(dá)式與常見(jiàn)的功能接口結(jié)合使用

在本節(jié)中,我們將討論一些常見(jiàn)的功能接口如何與 lambda 表達(dá)式結(jié)合使用。

FileFilter 接口

FileFilter 接口具有單一方法 accept(),用于篩選文件。在 Java 教程 ImageFilter 示例中,ImageFilter 類實(shí)現(xiàn)了 FileFilter 接口,并提供了 accept() 方法的實(shí)現(xiàn)。accept() 方法用來(lái)使用 Utils 類只接受圖像文件(和目錄)。

我們可以使用一個(gè)返回 boolean 的 lambda 表達(dá)式提供 FileFilter 接口的實(shí)現(xiàn),如清單 2 所示。

import java.io.FileFilter;import java.io.File;public class ImageFilter {   public static void main(String[] args) {      FileFilter fileFilter = (f) -> {         String extension = null;         String s = f.getName();         int i = s.lastIndexOf('.');         if (i > 0 && i << s.length() - 1) {            extension = s.substring(i + 1).toLowerCase();         }         if (extension != null) {            if (extension.equals("tiff") || extension.equals("tif")               || extension.equals("gif") || extension.equals("jpeg")               || extension.equals("jpg") || extension.equals("png")               || extension.equals("bmp")) {            return true;         } else {            return false;         }         }         return false;      };      File file = new File("C:/JDK8/Figure10.bmp");      System.out.println("File is an image file: " + fileFilter.accept(file));   }}

清單 2

Eclipse 控制臺(tái)顯示了 ImageFilter 類的輸出,如圖 11 所示。


圖 11

Runnable 接口

Java 教程“定義和啟動(dòng)線程”一節(jié)中,HelloRunnable 類實(shí)現(xiàn)了 Runnable 接口,并使用 HelloRunnable 類的實(shí)例創(chuàng)建了 Thread??梢允褂?lambda 表達(dá)式創(chuàng)建 ThreadRunnable,如清單 3 所示。Lambda 表達(dá)式?jīng)]有 return 語(yǔ)句,因?yàn)?Runnablerun() 方法的結(jié)果是 void。

import java.lang.Runnable;public class HelloRunnable {   public static void main(String args[]) {      (new Thread(() -> {         System.out.println("Hello from a thread");      })).start();   }}

清單 3:

HelloRunnable 類的輸出如圖 12 所示。


圖 12

Callable 接口

如果我們創(chuàng)建了一個(gè)實(shí)現(xiàn) java.util.concurrent.Callable<V> 泛型功能接口的類,該類需要實(shí)現(xiàn) call() 方法。在清單 4 中,HelloCallable 類實(shí)現(xiàn)了參數(shù)化類型 Callable<String>。

package lambda;import java.util.concurrent.Callable;public class HelloCallable implements Callable<String> {   @Override   public String call() throws Exception {      return "Hello from Callable";   }   public static void main(String[] args) {      try {         HelloCallable helloCallable = new HelloCallable();         System.out.println(helloCallable.call());      } catch (Exception e) {         System.err.println(e.getMessage());      }   }}

清單 4

我們可以使用 lambda 表達(dá)式提供 call() 泛型方法的實(shí)現(xiàn)。由于 call() 方法不需要任何參數(shù),因此 lambda 表達(dá)式中的括號(hào)為空;由于該方法為參數(shù)化類型 Callable<String> 返回 String,所以 lambda 表達(dá)式必須返回 String。

import java.util.concurrent.Callable;public class HelloCallable {   public static void main(String[] args) {      try {         Callable<String> c = () -> "Hello from Callable";         System.out.println(c.call());      } catch (Exception e) {         System.err.println(e.getMessage());      }   }}

清單 5

HelloCallable 的輸出如圖 13 所示。


圖 13

PathMatcher 接口

java.nio.file.PathMatcher 接口用于匹配路徑。該功能接口具有單一方法 matches(Path path),用于匹配 Path。我們可以使用 lambda 表達(dá)式提供 matches() 方法的實(shí)現(xiàn),如清單 6 所示。Lambda 表達(dá)式返回 boolean,目標(biāo)類型是功能接口 PathMatcher。

import java.nio.file.PathMatcher;import java.nio.file.Path;import java.nio.file.FileSystems;public class FileMatcher {   public static void main(String[] args) {      PathMatcher matcher = (f) -> {         boolean fileMatch = false;         String path = f.toString();         if (path.endsWith("HelloCallable.java"))            fileMatch = true;         return fileMatch;      };      Path filename = FileSystems.getDefault().getPath(            "C:/JDK8/HelloCallable.java");      System.out.println("Path matches: " + matcher.matches(filename));   }}

清單 6

FileMatcher 類的輸出如圖 14 所示。


圖 14

Comparator 接口

功能接口 Comparator 具有單一方法:compares()。雖然該接口還有 equals() 方法,但 equals() 方法也存在于 Object 類中。除了另一個(gè)方法外,功能接口還可以有 Object 類方法。如果我們使用 Comparator 比較 Employee 實(shí)體的實(shí)例,首先需要定義 Employee POJO,它具有 empIdfirstNamelastName 屬性和針對(duì)這些屬性的 getter/setter 方法,如清單 7 所示。

import java.util.*;public class Employee {   private int empId;   private String lastName;   private String firstName;       public Employee() {   }   public Employee(int empId, String lastName, String firstName) {      this.empId = empId;      this.firstName = firstName;      this.lastName = lastName;   }      // setters and getters   public int getEmpId() {      return empId;   }   public void setEmpId(int empId) {      this.empId = empId;   }   public String getLastName() {      return lastName;   }   public void setLastName(String lastName) {      this.lastName = lastName;   }   public String getFirstName() {      return firstName;   }   public void setFirstName(String firstName) {      this.firstName = firstName;   }   public  int compareByLastName(Employee x, Employee y)    {       return x.getLastName().compareTo(y.getLastName());    }   /**    *     * public static int compareByLastName(Employee x, Employee y)    {       return x.getLastName().compareTo(y.getLastName());    }    */}

清單 7

如清單 8 所示,創(chuàng)建一個(gè)名為 EmployeeSort 的類,根據(jù) lastName 對(duì) Employee 實(shí)體的 List 進(jìn)行排序。在 EmployeeSort 類中,創(chuàng)建 List 對(duì)象并向其添加 Employee 對(duì)象。使用 Collections.sort 方法對(duì) List 進(jìn)行排序,并使用匿名內(nèi)部類為 sort() 方法創(chuàng)建 Comparator 對(duì)象。

package lambda;import java.util.*;public class EmployeeSort {   public static void main(String[] args) {      Employee e1 = new Employee(1, "Smith", "John");      Employee e2 = new Employee(2, "Bloggs", "Joe");      List<Employee> list = new ArrayList<Employee>();      list.add(e1);      list.add(e2);      Collections.sort(list, new Comparator<Employee>() {         public int compare(Employee x, Employee y) {            return x.getLastName().compareTo(y.getLastName());         }      });      ListIterator<Employee> litr = list.listIterator();      while (litr.hasNext()) {         Employee element = litr.next();         System.out.print(element.getLastName() + " ");      }   }}

清單 8

可以用 lambda 表達(dá)式替換匿名內(nèi)部類,該表達(dá)式有兩個(gè) Employee 類型參數(shù),根據(jù) lastName 比較返回 int 值,如清單 9 所示。

import java.util.*;public class EmployeeSort {   public static void main(String[] args) {      Employee e1 = new Employee(1, "Smith", "John");      Employee e2 = new Employee(2, "Bloggs", "Joe");      List<Employee> list = new ArrayList<Employee>();      list.add(e1);      list.add(e2);      Collections.sort(list,            (x, y) -> x.getLastName().compareTo(y.getLastName()));      ListIterator<Employee> litr = list.listIterator();       while (litr.hasNext()) {         Employee element = litr.next();         System.out.print(element.getLastName() + " ");      }   }}

清單 9

EmployeeSort 的輸出如圖 15 所示。


圖 15

如何推斷目標(biāo)類型和 Lambda 參數(shù)類型?

對(duì)于 lambda 表達(dá)式,從上下文推斷目標(biāo)類型。因此,lambda 表達(dá)式只能用在可以推斷目標(biāo)類型的上下文中。這類上下文包括:變量聲明、賦值語(yǔ)句、return 語(yǔ)句、數(shù)組初始值設(shè)定項(xiàng)、方法或構(gòu)造函數(shù)的參數(shù)、lambda 表達(dá)式主體、條件表達(dá)式和轉(zhuǎn)換表達(dá)式。

Lambda 的形式參數(shù)類型也從上下文推斷。除了清單 1 中的 Hello 示例,在前面的所有示例中,參數(shù)類型都是從上下文推斷出來(lái)的。在后續(xù)章節(jié)中,我們將討論可以使用 lambda 表達(dá)式的上下文。

return 語(yǔ)句中的 Lambda 表達(dá)式

Lambda 表達(dá)式可以用在 return 語(yǔ)句中。return 語(yǔ)句中使用 lambda 表達(dá)式的方法的返回類型必須是一個(gè)功能接口。例如,返回 Runnable 的方法的 return 語(yǔ)句中包含一個(gè) lambda 表達(dá)式,如清單 10 所示。

import java.lang.Runnable;public class HelloRunnable2 {   public static Runnable getRunnable() {      return () -> {         System.out.println("Hello from a thread");      };   }   public static void main(String args[]) {      new Thread(getRunnable()).start();   }}

清單 10

Lambda 表達(dá)式未聲明任何參數(shù),因?yàn)?Runnable 接口的 run() 方法沒(méi)有聲明任何形式參數(shù)。Lambda 表達(dá)式不返回值,因?yàn)?run() 方法的結(jié)果是 void。清單 10 的輸出如圖 16 所示。


圖 16

Lambda 表達(dá)式作為目標(biāo)類型

Lambda 表達(dá)式本身可以用作內(nèi)部 lambda 表達(dá)式的目標(biāo)類型。Callable 中的 call() 方法返回一個(gè) Object,但是 Runnable 中的 run() 方法沒(méi)有返回類型。在 HelloCallable2 類中,內(nèi)部 lambda 表達(dá)式的目標(biāo)類型是 Runnable,外部 lambda 表達(dá)式的目標(biāo)類型是 Callable。目標(biāo)類型是從上下文(對(duì) Callable<Runnable> 類型的引用變量進(jìn)行賦值)推斷出來(lái)的。

在清單 11 中,內(nèi)部 lambda 表達(dá)式 () -> {System.out.println("Hello from Callable");} 的類型被推斷為 Runnable,因?yàn)閰?shù)列表為空,并且結(jié)果是 void;匿名方法簽名和結(jié)果與 Runnable 接口中的 run() 方法相同。外部 lambda 表達(dá)式 () -> Runnable 的類型被推斷為 Callable<Runnable>,因?yàn)?Callable<V> 中的 call() 方法沒(méi)有聲明任何形式參數(shù),并且結(jié)果類型是類型參數(shù) V。

import java.util.concurrent.Callable;public class HelloCallable2 {   public static void main(String[] args) {      try {         Callable<Runnable> c = () -> () -> {            System.out.println("Hello from Callable");         };          c.call().run();      } catch (Exception e) {         System.err.println(e.getMessage());      }   }}

清單 11

HelloCallable2 的輸出如圖 17 所示。


圖 17

數(shù)組初始值設(shè)定項(xiàng)中的 Lambda 表達(dá)式

Lambda 表達(dá)式可以用在數(shù)組初始值設(shè)定項(xiàng)中,但不能使用泛型數(shù)組初始值設(shè)定項(xiàng)。例如,以下泛型數(shù)組初始值設(shè)定項(xiàng)中的 lambda 表達(dá)式將產(chǎn)生編譯器錯(cuò)誤:

Callable<String>[] c=new Callable<String>[]{ ()->"a", ()->"b", ()->"c" };

將產(chǎn)生編譯器錯(cuò)誤 Cannot create a generic array of Callable<String>,如圖 18 所示。


圖 18

要在數(shù)組初始值設(shè)定項(xiàng)中使用 lambda 表達(dá)式,請(qǐng)指定一個(gè)非泛型數(shù)組初始值設(shè)定項(xiàng),如清單 12 所示的 CallableArray 類。

import java.util.concurrent.Callable;public class CallableArray  {public static void main(String[] args) {try{Callable<String>[] c=new Callable[]{ ()->"Hello from Callable a", ()->"Hello from Callable b", ()->"Hello from Callable c" };System.out.println(c[1].call());}catch(Exception e){System.err.println(e.getMessage());}}}

清單 12:

CallableArray 中的每個(gè)數(shù)組初始值設(shè)定項(xiàng)變量都是一個(gè) Callable<String> 類型的 lambda 表達(dá)式。Lambda 表達(dá)式的參數(shù)列表為空,并且 lambda 表達(dá)式的結(jié)果是一個(gè) String 類型的表達(dá)式。每個(gè) lambda 表達(dá)式的目標(biāo)類型從上下文推斷為 Callable<String> 類型。CallableArray 的輸出如圖 19 所示。


圖 19

轉(zhuǎn)換 Lambda 表達(dá)式

Lambda 表達(dá)式的目標(biāo)類型有時(shí)可能并不明確。例如,在下面的賦值語(yǔ)句中,lambda 表達(dá)式用作 AccessController.doPrivileged 方法的方法參數(shù)。Lambda 表達(dá)式的目標(biāo)類型不明確,因?yàn)槎鄠€(gè)功能接口(PrivilegedActionPrivilegedExceptionAction)都可以是 lambda 表達(dá)式的目標(biāo)類型。

String user = AccessController.doPrivileged(() -> System.getProperty("user.name"));

將產(chǎn)生編譯器錯(cuò)誤 The method doPrivileged(PrivilegedAction<String>) is ambiguous for the type AccessController,如圖 20 所示。


圖 20

我們可以使用 lambda 表達(dá)式的轉(zhuǎn)換將目標(biāo)類型指定為 PrivilegedAction<String>,如清單 13 所示的 UserPermissions 類。

import java.security.AccessController;import java.security.PrivilegedAction;public class UserPermissions {   public static void main(String[] args) {      String user = AccessController            .doPrivileged((PrivilegedAction<String>) () -> System               .getProperty("user.name"));      System.out.println(user);   }}

清單 13

Lambda 表達(dá)式中使用轉(zhuǎn)換后的 UserPermissions 輸出如圖 21 所示。


圖 21

條件表達(dá)式中的 Lambda 表達(dá)式

Lambda 表達(dá)式可以用在三元條件表達(dá)式中,后者的值是這兩個(gè)操作數(shù)中的任何一個(gè),具體取決于 boolean 條件為 true 還是 false。

在清單 14 所示的 HelloCallableConditional 類中,lambda 表達(dá)式 () -> "Hello from Callable:flag true")() -> "Hello from Callable:flag false") 構(gòu)成了用于賦值的這兩個(gè)可供選擇的表達(dá)式。Lambda 表達(dá)式的目標(biāo)類型是從上下文(對(duì) Callable<String> 引用變量進(jìn)行賦值)推斷出來(lái)的。隨后,使用該引用變量調(diào)用 call() 方法。

import java.util.concurrent.Callablepublic class HelloCallableConditional {   public static void main(String[] args) {      try {         boolean flag = true;         Callable<String> c = flag ? (() -> "Hello from Callable: flag true")               : (() -> "Hello from Callable: flag false");         System.out.println(c.call());      } catch (Exception e) {         System.err.println(e.getMessage());      }   }}

清單 14

HelloCallableConditional 的輸出如圖 22 所示。


圖 22

推斷重載方法中的目標(biāo)類型

調(diào)用重載方法時(shí),將使用與 lambda 表達(dá)式最匹配的方法。我們將使用目標(biāo)類型和方法參數(shù)類型選擇最佳方法。

在清單 15 的 HelloRunnableOrCallable 類中,指定了兩個(gè)返回類型為 Stringhello() 方法(hello() 方法被重載):它們的參數(shù)類型分別是 CallableRunnable。

將調(diào)用 hello() 方法,其中 lambda 表達(dá)式作為方法參數(shù)。由于 lambda 表達(dá)式 () -> "Hello Lambda" 返回 String,因此會(huì)調(diào)用 hello(Callable) 方法并輸出 Hello from Callable,因?yàn)?Callablecall() 方法具有返回類型,而 Runnablerun() 方法沒(méi)有。

import java.util.concurrent.Callable;public class HelloRunnableOrCallable {   static String hello(Runnable r) {      return "Hello from Runnable";   }   static String hello(Callable c) {      return "Hello from Callable";   }   public static void main(String[] args) {      String hello = hello(() -> "Hello Lambda");      System.out.println(hello);   }}

清單 15

HelloCallableConditional 的輸出如圖 23 所示。


圖 23

Lambda 表達(dá)式中的 this

在 lambda 表達(dá)式外面,this 引用當(dāng)前對(duì)象。在 lambda 表達(dá)式里面,this 引用封閉的當(dāng)前對(duì)象。不引用封閉實(shí)例成員的 lambda 表達(dá)式不會(huì)存儲(chǔ)對(duì)該成員的強(qiáng)引用,這解決了內(nèi)部類實(shí)例保留對(duì)封閉類的強(qiáng)引用時(shí)經(jīng)常出現(xiàn)的內(nèi)存泄漏問(wèn)題。

在清單 16 所示的示例中,Runnable 是 lambda 表達(dá)式的目標(biāo)類型。在 lambda 表達(dá)式主體中,指定了對(duì) this 的引用。創(chuàng)建 Runnable r 實(shí)例并調(diào)用 run() 方法后,this 引用將調(diào)用封閉實(shí)例,并從 toString() 方法獲得封閉實(shí)例的 String 值。將輸出 Hello from Class HelloLambda 消息。

public class HelloLambda {     Runnable r = () -> { System.out.println(this); };       public String toString() { return "Hello from Class HelloLambda"; }     public static void main(String args[]) {       new HelloLambda().r.run();            }   }

清單 16

HelloLambda 的輸出如圖 24 所示。


圖 24

Lambda 表達(dá)式的參數(shù)名稱

為 lambda 表達(dá)式的形式參數(shù)創(chuàng)建新名稱。如果用作 lambda 表達(dá)式參數(shù)名稱的名稱與封閉上下文中局部變量名稱相同,將產(chǎn)生編譯器錯(cuò)誤。在以下示例中,lambda 表達(dá)式的參數(shù)名稱被指定為 e1e2,它們同時(shí)還用于局部變量 Employee e1Employee e2。

Employee e1 = new Employee(1,"A", "C");     Employee e2 = new Employee(2,"B","D" );     List<Employee> list = new ArrayList<Employee>();     list.add(e1);list.add(e2);    Collections.sort(list, (Employee e1, Employee e2) -> e1.getLastName().compareTo(e2.getLastName()));

Lambda 表達(dá)式參數(shù) e1 將導(dǎo)致編譯器錯(cuò)誤,如圖 25 所示。


圖 25

Lambda 表達(dá)式參數(shù) e2 也將導(dǎo)致編譯器錯(cuò)誤,如圖 26 所示。


圖 26

對(duì)局部變量的引用

在提供匿名內(nèi)部類的替代方案時(shí),局部變量必須處于終態(tài)才能在 lambda 表達(dá)式中訪問(wèn)的要求被取消。JDK 8 也取消了局部變量必須處于終態(tài)才能從內(nèi)部類訪問(wèn)的要求。在 JDK 7 中,必須將從內(nèi)部類訪問(wèn)的局部變量聲明為終態(tài)。

在內(nèi)部類或 lambda 表達(dá)式中使用局部變量的要求已從“終態(tài)”修改為“終態(tài)或等效終態(tài)”。

方法引用

Lambda 表達(dá)式定義了一個(gè)匿名方法,其中功能接口作為目標(biāo)類型。可以使用方法引用來(lái)調(diào)用具有名稱的現(xiàn)有方法,而不是定義匿名方法。在清單 9 所示的 EmployeeSort 示例中,以下方法調(diào)用將 lambda 表達(dá)式作為方法參數(shù)。

Collections.sort(list, (x, y) -> x.getLastName().compareTo(y.getLastName()));

可以按以下方式將 lambda 表達(dá)式替換為方法引用:

Collections.sort(list, Employee::compareByLastName);

分隔符 (::) 可用于方法引用。compareByLastName 方法是 Employee 類中的靜態(tài)方法。

public static int compareByLastName(Employee x, Employee y) { return x.getLastName().compareTo(y.getLastName()); 

對(duì)于非靜態(tài)方法,方法引用可與特定對(duì)象的實(shí)例一起使用。通過(guò)將 compareByLastName 方法非靜態(tài)化,可按如下方式將方法引用與與 Employee 實(shí)例結(jié)合使用:

Employee employee=new Employee();Collections.sort(list, employee::compareByLastName); 

方法引用甚至不必是所屬對(duì)象的實(shí)例方法。方法引用可以是任何任意類對(duì)象的實(shí)例方法。例如,通過(guò)方法引用,可以使用 String 類的 compareTo 方法對(duì) String List 進(jìn)行排序。

String e1 = new String("A");     String e2 = new String("B");      List<String> list = new ArrayList<String>();     list.add(e1);list.add(e2);Collections.sort(list, String::compareTo);

方法引用是 lambda 表達(dá)式的進(jìn)一步簡(jiǎn)化。

構(gòu)造函數(shù)引用

方法引用的作用是方法調(diào)用,而構(gòu)造函數(shù)引用的作用是構(gòu)造函數(shù)調(diào)用。方法引用和構(gòu)造函數(shù)引用是 lambda 轉(zhuǎn)換,方法引用和構(gòu)造函數(shù)引用的目標(biāo)類型必須是功能接口。

在本節(jié)中,我們將以 Multimap 為例討論構(gòu)造函數(shù)引用。Multimap 是一個(gè) Google Collections 實(shí)用程序。圖像類型的 Multimap 按如下方式創(chuàng)建:

Multimap<ImageTypeEnum, String> imageTypeMultiMap = Multimaps        .newListMultimap(              Maps.<ImageTypeEnum, Collection<String>> newHashMap(),              new Supplier<List<String>>() { public List<String> get() {         return new ArrayList<String>();             }         });

Multimap 示例中,使用構(gòu)造函數(shù)按如下方式創(chuàng)建 Supplier<List<String>>

new Supplier<List<String>>() {             public List<String> get() {                 return new ArrayList<String>();             }         }

構(gòu)造函數(shù)返回 ArrayList<String>。通過(guò)構(gòu)造函數(shù)引用,可以使用簡(jiǎn)化的語(yǔ)法按如下方式創(chuàng)建 Multimap

Multimap<ImageTypeEnum, String> imageTypeMultiMap = Multimaps.newListMultimap(Maps.<ImageTypeEnum, Collection<String>> newHashMap(),ArrayList<String>::new); 

清單 17 顯示了使用構(gòu)造函數(shù)引用的 Multimap 示例,即 ImageTypeMultiMap 類。

import java.util.ArrayList;import java.util.Collection;import java.util.List;import com.google.common.base.Supplier;import com.google.common.collect.Maps;import com.google.common.collect.Multimap;import com.google.common.collect.Multimaps;public class ImageTypeMultiMap {   enum ImageTypeEnum {      tiff, tif, gif, jpeg, jpg, png, bmp   }   public static void main(String[] args) {      Multimap<ImageTypeEnum, String> imageTypeMultiMap = Multimaps            .newListMultimap(                  Maps.<ImageTypeEnum, Collection<String>> newHashMap(),               ArrayList<String>::new);      imageTypeMultiMap.put(ImageTypeEnum.tiff, "tiff");      imageTypeMultiMap.put(ImageTypeEnum.tif, "tif");      imageTypeMultiMap.put(ImageTypeEnum.gif, "gif");      imageTypeMultiMap.put(ImageTypeEnum.jpeg, "jpeg");      imageTypeMultiMap.put(ImageTypeEnum.jpg, "jpg");      imageTypeMultiMap.put(ImageTypeEnum.png, "png");      imageTypeMultiMap.put(ImageTypeEnum.bmp, "bmp");      System.out.println("Result: " + imageTypeMultiMap);   }}

清單 17

要測(cè)試 ImageTypeMultiMap,我們需要從 https://code.google.com/p/guava-libraries/ 下載 Guava 庫(kù) guava-14.0.1.jar,并將 guava-14.0.1.jar 添加到 Java 構(gòu)建路徑。ImageTypeMultiMap 的輸出如圖 27 所示。


圖 27

虛擬擴(kuò)展方法

接口的封裝和可重用性是接口的主要優(yōu)點(diǎn)。但接口的缺點(diǎn)是實(shí)現(xiàn)接口的類必須實(shí)現(xiàn)所有接口方法。有時(shí)只需要接口的部分方法,但在實(shí)現(xiàn)接口時(shí)必須提供所有接口方法的方法實(shí)現(xiàn)。虛擬擴(kuò)展方法解決了這個(gè)問(wèn)題。

虛擬擴(kuò)展方法是接口中具有默認(rèn)實(shí)現(xiàn)的方法。如果實(shí)現(xiàn)類不提供方法的實(shí)現(xiàn),則使用默認(rèn)的實(shí)現(xiàn)。實(shí)現(xiàn)類可以重寫默認(rèn)實(shí)現(xiàn),或提供新的默認(rèn)實(shí)現(xiàn)。

虛擬擴(kuò)展方法添加配置來(lái)擴(kuò)展接口的功能,而不會(huì)破壞已實(shí)現(xiàn)接口較早版本的類的向后兼容性。虛擬擴(kuò)展方法中的默認(rèn)實(shí)現(xiàn)是用 default 關(guān)鍵字提供的。由于虛擬擴(kuò)展方法提供默認(rèn)實(shí)現(xiàn),因此不能是抽象方法。

JDK 8 中的 java.util.Map<K,V> 類提供了幾個(gè)具有默認(rèn)實(shí)現(xiàn)的方法:

  • default V getOrDefault(Object key,V defaultValue)
  • default void forEach(BiConsumer<? super K,? super V> action)
  • default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
  • default V putIfAbsent(K key,V value)
  • default boolean remove(Object key,Object value)
  • default boolean replace(K key,V oldValue,V newValue)
  • default V replace(K key,V value)
  • default V computeIfAbsent(K key,Function<? super K,? extends V> mappingFunction)
  • default V computeIfPresent(K key,BiFunction<? super K,? super V,? extends V> remappingFunction)
  • default V compute(K key,BiFunction<? super K,? super V,? extends V> remappingFunction)
  • default V merge(K key,V value,BiFunction<? super V,? super V,? extends V> remappingFunction)

要證明類在實(shí)現(xiàn)接口時(shí)無(wú)需實(shí)現(xiàn)具有默認(rèn)實(shí)現(xiàn)的方法,請(qǐng)創(chuàng)建實(shí)現(xiàn) Map<K,V> 接口的 MapImpl 類:

public class MapImpl<K,V> implements Map<K, V> {

}

清單 18 顯示了完整的 MapImpl 類,實(shí)現(xiàn)了不提供默認(rèn)實(shí)現(xiàn)的方法。

import java.util.Collection;import java.util.Map;import java.util.Set;public class MapImpl<K,V> implements Map<K, V> {   public static void main(String[] args) {   }   @Override   public int size() {       return 0;   }   @Override   public boolean isEmpty() {       return false;   }   @Override   public boolean containsKey(Object key) {      return false;   }   @Override   public boolean containsValue(Object value) {            return false;   }   @Override   public V get(Object key) {      return null;   }   @Override   public V put(K key, V value) {      return null;   }   @Override   public V remove(Object key) {      return null;   }   @Override   public void putAll(Map<? extends K, ? extends V> m) {   }   @Override   public void clear() {	   }   @Override   public Set<K> keySet() {      return null;   }   @Override   public Collection<V> values() {      return null;   }   @Override   public Set<java.util.Map.Entry<K, V>> entrySet() {      return null;   }}

清單 18

雖然 Map<K,V> 接口是一個(gè)預(yù)定義的接口,但也可以使用虛擬擴(kuò)展方法定義一個(gè)新接口。使用 default 關(guān)鍵字創(chuàng)建為所有方法提供默認(rèn)實(shí)現(xiàn)的 EmployeeDefault 接口,如清單 19 所示。

public interface EmployeeDefault {   String name = "John Smith";   String title = "PHP Developer";   String dept = "PHP";   default  void setName(String name) {      System.out.println(name);   }   default String getName() {      return name;   }   default void setTitle(String title) {      System.out.println(title);   }   default String getTitle() {      return title;   }   default void setDept(String dept) {      System.out.println(dept);   }   default String getDept() {      return dept;   }}

清單 19

如果使用 default 關(guān)鍵字聲明接口方法,則該方法必須按編譯器錯(cuò)誤指出的方式提供實(shí)現(xiàn),如圖 28 所示。


圖 28

默認(rèn)情況下,接口的字段處于終態(tài),不能在默認(rèn)方法的默認(rèn)實(shí)現(xiàn)中賦值,如圖 29 中的編譯器錯(cuò)誤所示。


圖 29

沒(méi)有實(shí)現(xiàn) EmployeeDefault 接口的類,也可以提供虛擬擴(kuò)展方法的實(shí)現(xiàn)。EmployeeDefaultImpl 類實(shí)現(xiàn)了 EmployeeDefault 接口,沒(méi)有為從 EmployeeDefault 繼承的任何虛擬擴(kuò)展方法提供實(shí)現(xiàn)。EmployeeDefaultImpl 類使用方法調(diào)用表達(dá)式調(diào)用虛擬擴(kuò)展方法,如清單 20 所示。

public class EmployeeDefaultImpl implements EmployeeDefault {   public static void main(String[] args) {       EmployeeDefaultImpl employeeDefaultImpl=new EmployeeDefaultImpl();      System.out.println(employeeDefaultImpl.getName());      System.out.println(employeeDefaultImpl.getTitle());      System.out.println(employeeDefaultImpl.getDept());   }}

清單 20

總結(jié)

本文介紹了 JDK 8 的新特性 — lambda 表達(dá)式,其語(yǔ)法簡(jiǎn)潔,是匿名類的簡(jiǎn)短形式。此外,還介紹了虛擬擴(kuò)展方法,它非常有用,因?yàn)樗峁┝司哂心J(rèn)方法實(shí)現(xiàn)的接口;如果實(shí)現(xiàn)類不提供方法的實(shí)現(xiàn),則會(huì)使用該默認(rèn)的方法實(shí)現(xiàn)。

另請(qǐng)參見(jiàn)

Lambda 表達(dá)式

關(guān)于作者

Deepak Vohra 是一名 NuBean 顧問(wèn)、Web 開(kāi)發(fā)人員、Sun 認(rèn)證的 Java 1.4 程序員和 Sun 認(rèn)證的 Java EE Web 組件開(kāi)發(fā)人員。

分享交流

請(qǐng)?jiān)?FacebookTwitterOracle Java 博客上加入 Java 社區(qū)對(duì)話!

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
JDK 8 中的新特性(官網(wǎng))
Java核心技術(shù) PDF 高清電子書(shū)
JAVA8之lambda表達(dá)式詳解
源碼元宇宙-lambda表達(dá)式底層執(zhí)行解析
【小家java】java8新特性(簡(jiǎn)述十大新特性)
編寫高質(zhì)量代碼改善C#程序的157個(gè)建議
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服