1. 程式人生 > >Java8 Lambda表示式詳解手冊及例項

Java8 Lambda表示式詳解手冊及例項

先販賣一下焦慮,Java8發於2014年3月18日,距離現在已經快6年了,如果你對Java8的新特性還沒有應用,甚至還一無所知,那你真得關注公眾號“程式新視界”,好好系列的學習一下Java8的新特性。Lambda表示式已經在新框架中普通使用了,如果你對Lambda還一無所知,真得認真學習一下本篇文章了。

現在進入正題Java8的Lambda,首先看一下發音 ([ˈlæmdə])表示式。注意該詞的發音,b是不發音的,da發[də]音。

為什麼要引入Lambda表示式

簡單的來說,引入Lambda就是為了簡化程式碼,允許把函式作為一個方法的引數傳遞進方法中。如果有JavaScript的程式設計經驗,馬上會想到這不就是閉包嗎。是的,Lambda表示式也可以稱作Java中的閉包。

先回顧一下Java8以前,如果想把某個介面的實現類作為引數傳遞給一個方法會怎麼做?要麼建立一個類實現該介面,然後new出一個物件,在呼叫方法時傳遞進去,要麼使用匿名類,可以精簡一些程式碼。以建立一個執行緒並列印一行日誌為例,使用匿名函式寫法如下:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("歡迎關注公眾號:程式新視界");
    }
}).start();

在java8以前,使用匿名函式已經算是很簡潔的寫法了,再來看看使用Lambda表示式,上面的程式碼會變成什麼樣子。

new Thread(() -> System.out.println("歡迎關注公眾號:程式新視界")).start();

是不是簡潔到爆!

我們都知道java是面向物件的程式語言,除了部分簡單資料型別,萬物皆物件。因此,在Java中定義函式或方法都離不開物件,也就意味著很難直接將方法或函式像引數一樣傳遞,而Java8中的Lambda表示式的出現解決了這個問題。

Lambda表示式使得Java擁有了函數語言程式設計的能力,但在Java中Lambda表示式是物件,它必須依附於一類特別的物件型別——函式式介面(functional interface),後面詳細講解。

Lambda表示式簡介

Lambda表示式是一種匿名函式(對Java而言這並不完全準確),通俗的說,它是沒有宣告的方法,即沒有訪問修飾符、返回值宣告和名字的方法。使用Lambda表示式的好處很明顯就是可以使程式碼變的更加簡潔緊湊。

Lambda表示式的使用場景與匿名類的使用場景幾乎一致,都是在某個功能(方法)只使用一次的時候。

Lambda表示式語法結構

Lambda表示式通常使用(param)->(body)語法書寫,基本格式如下:

//沒有引數
() -> body

// 1個引數
(param) -> body
// 或
(param) ->{ body; }

// 多個引數
(param1, param2...) -> { body }
// 或
(type1 param1, type2 param2...) -> { body }

常見的Lambda表示式如下:

// 無引數,返回值為字串“公眾號:程式新視界”
() -> "公眾號:程式新視界";

// 1個String引數,直接列印結果
(System.out::println);
// 或
(String s) -> System.out.print(s)

// 1個引數(數字),返回2倍值  
x -> 2 * x;

// 2個引數(數字),返回差值
(x, y) -> x – y
  
// 2個int型整數,返回和值  
(int x, int y) -> x + y

對照上面的示例,我們再總結一下Lambda表示式的結構:

  • Lambda表示式可以有0~n個引數。
  • 引數型別可以顯式宣告,也可以讓編譯器從上下文自動推斷型別。如(int x)和(x)是等價的。
  • 多個引數用小括號括起來,逗號分隔。一個引數可以不用括號。
  • 沒有引數用空括號表示。
  • Lambda表示式的正文可以包含零條,一條或多條語句,如果有返回值則必須包含返回值語句。如果只有一條可省略大括號。如果有一條以上則必須包含在大括號(程式碼塊)中。

函式式介面

函式式介面(Functional Interface)是Java8對一類特殊型別的介面的稱呼。這類介面只定義了唯一的抽象方法的介面(除了隱含的Object物件的公共方法),因此最開始也就做SAM型別的介面(Single Abstract Method)。

比如上面示例中的java.lang.Runnable就是一種函式式介面,在其內部只定義了一個void run()的抽象方法,同時在該介面上註解了@FunctionalInterface。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

@FunctionalInterface註解是用來表示該介面要符合函式式介面的規範,除了隱含的Object物件的公共方法以外只可有一個抽象方法。當然,如果某個介面只定義一個抽象方法,不使用該註解也是可以使用Lambda表示式的,但是沒有該註解的約束,後期可能會新增其他的抽象方法,導致已經使用Lambda表示式的地方出錯。使用@FunctionalInterface從編譯層面解決了可能的錯誤。

比如當註解@FunctionalInterface之後,寫兩個抽象方法在介面內,會出現以下提示:

Multiple non-overriding abstract methods found in interface com.secbro2.lambda.NoParamInterface

通過函式式介面我們也可以得出一個簡單的結論:可使用Lambda表示式的介面,只能有一個抽象方法(除了隱含的Object物件的公共方法)。

注意此處的方法限制為抽象方法,如果介面內有其他靜態方法則不會受限制。

方法引用,雙冒號操作

[方法引用]的格式是,類名::方法名。

像如ClassName::methodName或者objectName::methodName的表示式,我們把它叫做方法引用(Method Reference),通常用在Lambda表達中。

看一下示例:

// 無引數情況
NoParamInterface paramInterface2 = ()-> new HashMap<>();
// 可替換為
NoParamInterface paramInterface1 = HashMap::new;

// 一個引數情況
OneParamInterface oneParamInterface1 = (String string) -> System.out.print(string);
// 可替換為
OneParamInterface oneParamInterface2 = (System.out::println);

// 兩個引數情況
Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge());
// 可替換為
Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge());
// 進一步可替換為
Comparator c = Comparator.comparing(Computer::getAge);

再比如我們用函式式介面java.util.function.Function來實現一個String轉Integer的功能,可以如下寫法:

Function<String, Integer> function = Integer::parseInt;
Integer num = function.apply("1");

根據Function介面的定義Function<T,R>,其中T表示傳入型別,R表示返回型別。具體就是實現了Function的apply方法,在其方法內呼叫了Integer.parseInt方法。

通過上面的講解,基本的語法已經完成,以下內容通過例項來逐一演示在不同的場景下如何使用。

Runnable執行緒初始化示例

Runnable執行緒初始化是比較典型的應用場景。

// 匿名函類寫法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("歡迎關注公眾號:程式新視界");
    }
}).start();

// lambda表示式寫法
new Thread(() -> System.out.println("歡迎關注公眾號:程式新視界")).start();

// lambda表示式 如果方法體內有多行程式碼需要帶大括號
new Thread(() -> {
    System.out.println("歡迎關注公眾號");
    System.out.println("程式新視界");
}).start();

通常都會把lambda表示式內部變數的名字起得短一些,這樣能使程式碼更簡短。

事件處理示例

Swing API程式設計中經常會用到的事件監聽。

// 匿名函類寫法
JButton follow =  new JButton("關注");
follow.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("已關注公眾號:程式新視界");
    }
});

// lambda表示式寫法
follow.addActionListener((e) -> System.out.println("已關注公眾號:程式新視界"));

// lambda表示式寫法
follow.addActionListener((e) -> {
    System.out.println("已關注公眾號");
    System.out.println("程式新視界");
});

列表遍歷輸出示例

傳統遍歷一個List,基本上都使用for迴圈來遍歷,Java8之後List擁有了forEach方法,可配合lambda表示式寫出更加簡潔的方法。

List<String> list = Arrays.asList("歡迎","關注","程式新視界");

// 傳統遍歷
for(String str : list){
    System.out.println(str);
}

// lambda表示式寫法
list.forEach(str -> System.out.println(str));
// lambda表示式寫法
list.forEach(System.out::println);

函式式介面示例

在上面的例子中已經看到函式式介面java.util.function.Function的使用,在java.util.function包下中還有其他的類,用來支援Java的函數語言程式設計。比如通過Predicate函式式介面以及lambda表示式,可以向API方法新增邏輯,用更少的程式碼支援更多的動態行為。

@Test
public void testPredicate() {

    List<String> list = Arrays.asList("歡迎", "關注", "程式新視界");

    filter(list, (str) -> ("程式新視界".equals(str)));

    filter(list, (str) -> (((String) str).length() == 5));

}

public static void filter(List<String> list, Predicate condition) {
    for (String content : list) {
        if (condition.test(content)) {
            System.out.println("符合條件的內容:" + content);
        }
    }
}

其中filter方法中的寫法還可以進一步簡化:

list.stream().filter((content) -> condition.test(content)).forEach((content) ->System.out.println("符合條件的內容:" + content));

list.stream().filter(condition::test).forEach((content) ->System.out.println("符合條件的內容:" + content));

list.stream().filter(condition).forEach((content) ->System.out.println("符合條件的內容:" + content));

如果不需要“符合條件的內容:”字串的拼接,還能夠進一步簡化:

list.stream().filter(condition).forEach(System.out::println);

如果將呼叫filter方法的判斷條件也寫在一起,test方法中的內容可以通過一行程式碼來實現:

list.stream().filter((str) -> ("程式新視界".equals(str))).forEach(System.out::println);

如果需要同時滿足兩個條件或滿足其中一個即可,Predicate可以將這樣的多個條件合併成一個。

Predicate start = (str) -> (((String) str).startsWith("程式"));
Predicate len = (str) -> (((String) str).length() == 5);

list.stream().filter(start.and(len)).forEach(System.out::println);

Stream相關示例

在《JAVA8 STREAM新特性詳解及實戰》一文中已經講解了Stream的使用。你是否發現Stream的使用都離不開Lambda表示式。是的,所有Stream的操作必須以Lambda表示式為引數。

以Stream的map方法為例:

Stream.of("a","b","c").map(item -> item.toUpperCase()).forEach(System.out::println);
Stream.of("a","b","c").map(String::toUpperCase).forEach(System.out::println);

更多的使用例項可參看Stream的《JAVA8 STREAM新特性詳解及實戰》一文。

Lambda表示式與匿名類的區別

  • 關鍵詞的區別:對於匿名類,關鍵詞this指向匿名類,而對於Lambda表示式,關鍵詞this指向包圍Lambda表示式的類的外部類,也就是說跟表示式外面使用this表達的意思是一樣。
  • 編譯方式:Java編譯器編譯Lambda表示式時,會將其轉換為類的私有方法,再進行動態繫結,通過invokedynamic指令進行呼叫。而匿名內部類仍然是一個類,編譯時編譯器會自動為該類取名並生成class檔案。

其中第一條,以Spring Boot中ServletWebServerApplicationContext類的一段原始碼作為示例:

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
   return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}

其中,這裡的this指向的就是getSelfInitializer方法所在的類。

小結

至此,Java8 Lambda表示式的基本使用已經講解完畢,最關鍵的還是要勤加練習,達到熟能生巧的使用。當然,剛開始可能需要一個適應期,在此期間可以把本篇文章收藏當做一個手冊拿來參考。

原文連結:《Java8 Lambda表示式詳解手冊及例項》


程式新視界:精彩和成長都不容錯過

相關推薦

Java8 Lambda表示式手冊例項

先販賣一下焦慮,Java8發於2014年3月18日,距離現在已經快6年了,如果你對Java8的新特性還沒有應用,甚至還一無所知,那你真得關注公眾號“程式新視界”,好好系列的學習一下Java8的新特性。Lambda表示式已經在新框架中普通使用了,如果你對Lambda還一無所知,真得認真學習一下本篇文章了。 現在

Java8新特性Stream API與Lambda表示式(1)

1 為什麼需要Stream與Lambda表示式? 1.1  為什麼需要Stream Stream作為 Java 8 的一大亮點,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不同的概念。它也不同於 StAX 對 XML 解析的 S

Lambda表示式

前言         1、天真熱,程式設計師活著不易,星期天,也要頂著火辣辣的太陽,總結這些東西。         2、誇誇lambda吧:簡化了匿名委託的使用,讓你讓程式碼更加簡潔,優雅。據說它是微軟自c#1.0後新增的最重要的功能之一。 lambda簡介  

C++中lambda表示式與原理分析

lambda表示式的本質就是過載了()運算子的類,這種類通常被稱為functor,即行為像函式的類。因此lambda表示式物件其實就是一個匿名的functor。 C++中lambda表示式的構成 一個標準的lambda表示式包括:捕獲列表、引數列表、mu

C++中的Lambda表示式

我是搞C++的 一直都在提醒自己,我是搞C++的;但是當C++11出來這麼長時間了,我卻沒有跟著隊伍走,發現很對不起自己的身份,也還好,發現自己也有段時間沒有寫C++程式碼了。今天看到了C++中的Lambda表示式,雖然用過C#的,但是C++的,一直沒有用,也不知道怎麼

Lambda表示式(例子

前言         1、天真熱,程式設計師活著不易,星期天,也要頂著火辣辣的太陽,總結這些東西。         2、誇誇lambda吧:簡化了匿名委託的使用,讓你讓程式碼更加簡潔,優雅。據說它是微軟自c#1.0後新增的最重要的功能之一。 lambda簡介  

【JDK8】lambda表示式

(轉自:http://blog.csdn.net/ioriogami/article/details/12782141/) 1. 什麼是λ表示式 λ表示式本質上是一個匿名方法。讓我們來看下面這個例子:     public int add(int x, int

Java中lambda表示式

原文地址:http://blog.laofu.online/2018/04/20/java-lambda/ 為什麼使用lambda 在java中我們很容易將一個變數賦值,比如int a =0;int b=a; 但是我們如何將一段程式碼和一個函式賦值給一個變數?這個變數應該是什麼的型別? 在java

正則表示式實戰

Create by jsliang on 2018-11-14 10:41:20 Recently revised in 2018-11-19 09:04:18  Hello 小夥伴們,如果覺得本文還不錯,記得給個 star,你們的 star 是我學習的動力!GitHub 地址  正則表

JS中正則表示式最基本的判斷手機號,郵箱,身份證

    首先最基本的符號型別及含義整理一下。     1,最基本的符號:^  $  *  +  ?         ^    代表字串的開始位置 &nbs

springEL表示式應用

什麼是SpringEL? Spring3中引入了Spring表示式語言—SpringEL,SpEL是一種強大,簡潔的裝配Bean的方式,他可以通過執行期間執行的表示式將值裝配到我們的屬性或建構函式當中,更可以呼叫JDK中提供的靜態常量,獲取外部Properti

sed---用法解釋

nts parameter 同一行 開頭 types consul win 命令 one 1.sed -n ‘2‘p filename 打印文件的第二行。 2.sed -n ‘1,3‘p filename 打印文件的1到3行 3. sed -n ‘/Neave/‘p fil

Jsp的指令碼、宣告、表示式

jsp指令碼 在<%%>中包含可執行的Java程式碼 <% Java程式碼 %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding

php 中正則表示式

概述 正則表示式是一種描述字串結果的語法規則,是一個特定的格式化模式,可以匹配、替換、擷取匹配的字串。常用的語言基本上都有正則表示式,如JavaScript、java等。其實,只有瞭解一種語言的正則使用,其他語言的正則使用起來,就相對簡單些。文字主要圍繞解決下面問題展開。 有哪些常用的

Cron表示式表示式的驗證

本篇不算原創,因為主要內容來自網上的部落格,所以給出我參考文章的連結。 本文cron表示式詳解的大部分內容參考了[cron表示式詳解]和Quartz使用總結、Cron表示式 這兩篇文章。 cron校驗的內容參考了 判斷cron表示式輸入是否有效的正則表示式 和Verifying a cron expres

正則表示式(貪婪與懶惰、前瞻與後顧、後向引用等)

之前嫌正則麻煩,一直沒有深入去了解過正則,能不用的地方就不使用。 最近專案中遇到了不可避免的正則使用,所以花了點時間去了解並整理了一下,理解不一定完全準確,如有不對歡迎指出,希望對大家有所幫助。 一、名詞解釋 首先我們瞭解幾個名詞:元字元 、 普通字元、列印字元、非列印字元、 限定符 、定位符、非列

Spring排程器corn表示式

NAME crontab -- tables for driving cron DESCRIPTION A crontab file contains instructions to the cron(8) daemon of the general

Dubbo深度結合Zookeeper、SSM的RPC實戰

一、SOA 1.SOA概念 2.SOA定位 3.老的專案架構設計 3.1 企業專案不允許所有專案都訪問DB 3.2 開發時DB訪問層程式碼冗餘 4.使用SOA架構 4.1 專門訪問DB服務(專案) 4.2 開發時可以實現DB訪問層和程式碼複用 5.實現SOA的幾種

jdk8 lambda表示式總結 Java8 lambda表示式10個示例

Java8 lambda表示式10個示例    1. 實現Runnable執行緒案例 使用() -> {} 替代匿名類: //Before Java 8: new Thread(new Runnable() { @Override

JavaWeb開發之ServletServlet容器

由於 servlet開發 遊戲 metadata 移動互 -o 每一個 web開發 port 自JavaEE誕生伊始,Servlet容器和Servlet技術,就構成了JavaEE應用的核心,配合其它組件,它們完善了Java企業級開發的全套解決方案。小到一個靜態博客網站,大到