怎麼用一行 Lambda 表達式讓你的代碼更加優雅

1. 介紹一個新朋友 , Lambda 表達式

 

當我們要用 java 代碼實現一個計時器 ,要求每秒輸出一次當前時間 。我們會寫出這樣的一份代碼

 

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

/**
 * @author LDX
 * @description 報時器
 **/
public class TimeClick {
    public static void main(String[] args) {
        ActionListener listener = new TimePrinter();

        Timer t = new Timer(1000, listener);
        t.start();

        JOptionPane.showMessageDialog(null, "中止程序 ?");
        System.exit(0);
    }

    static class TimePrinter implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {
            System.out.println("現在的時間是 : " + new Date());
            Toolkit.getDefaultToolkit().beep();
        }

    }
}

這段代碼在功能上沒什麼問題 ,但是在內容上有那麼一點小缺陷 。

 

可以看到 。TimePrinter 這個類只包含的一個 3 行方法 ,我們卻要用 7 行的代碼將它包裝爲一個對象 ,再用 1 行代碼實例化 ,才能將它傳遞到主函數中執行 。

 

這是 Java 面向對象思想的體現,但總歸不夠優雅 ,而在其他的語言中 ,代碼段可以直接被處理 ,代碼也就更加簡潔 ,清晰 。

 

那麼 Java 中有沒有一種方法或特性可以讓 Java 也可以直接處理代碼段 ,從而使 Java 代碼變得簡潔 ,優雅 ,高效呢 ?

 

好消息 ! 好消息 ! Java 支持 Lambda 表達式啦 ,全新特性 ,全新寫法 ,幫助你從函數式接口(functional interface) 中解放出來 ,更精簡的代碼 ,更清晰的思路 ,趕快來嘗試一下吧 !

 

lambda 表達式是一個可傳遞的代碼塊 ,以計數器爲例 ,它長這樣 :

 

Timer t = new Timer(1000, event -> {
            System.out.println("現在的時間是 : " + new Date());
            Toolkit.getDefaultToolkit().beep();
        });

 

完整的程序爲 :

 

import javax.swing.*;
import java.awt.*;
import java.util.Date;

/**
 * @author LDX
 * @description 報時器
 **/
public class TimeClick {
    public static void main(String[] args) {

        // 第一個參數爲毫秒 ,定義調用時間間隔
        Timer t = new Timer(1000, event -> {
            System.out.println("現在的時間是 : " + new Date());
            Toolkit.getDefaultToolkit().beep();
        });

        // 啓動定時器
        t.start();

        // 顯示一箇中止對話框 ,第一個參數爲顯示位置
        JOptionPane.showMessageDialog(null, "中止程序 ?");
        System.exit(0);
    }

}

 

發現了嗎 ,lambda 表達式幫助我們省略了函數式接口以及對應的實例化 。

 

同時 ,你還可以看到 ,lanbda 表達式的本質就是一個代碼塊 ,以及必須傳入的代碼變量規範 。

 

下面就是一個完整的 lambda 表達式了 ,表達式分爲三個部分 。

 

(String first, String second) -> {
    if (first.length() < second.length()) return -1; 
    else if (first.length() > second.length()) return 1; 
    else return 0;
}

 

第一個部分是參數 ,包含在第一個 ( ) 中 ,需要給出參數類型以及參數名稱 。

 

第二部分是箭頭 ,‘ -> ’ , 連接參數和表達式 。

 

第三個部分是表達式 ,包含在 { } 中 ,可以包含顯式的 return語句 。

 

如果可以推導出一個 lambda 表達式的參數類型 ,那麼可以忽略其類型 。

 

也就是說 ,lambda 表達式還可以這麼寫 :

 

(first, second) -> {
    if (first.length() < second.length()) return -1; 
    else if (first.length() > second.length()) return 1; 
    else return 0;
}

 

如果表達式只有一個參數 ,且參數類型可以推導得出 ,那麼你甚至可以忽略小括號 ,讓 Java 自己去推導 。

 

ActionListener listener = event -> 
    System.out.println("現在的時間是 : " + new Date()"); 
    // Instead of (event) -> . . . or (ActionEvent event) ->

 

輸入參數的類型都交給 lambda 自己去猜了 ,那麼返回類型自然也不再需要我們自己去定義 。

 

lambda 的返回值類型總是會由上下文推導得出 ,不需要我們去指定 。

 

2. 當我們使用 Lambda 表達式 ,我們應該想到什麼

 

首先 ,我們要明白 Lambda 表達式可以用在哪些地方 。

 

Java 中已經有很多封裝代碼塊的接口 , 如 ActionListener 或 Comparator ,lambda 表達式與這些接口是兼容的 。

 

對於只有一個抽象方法的接口 ,我們稱之爲 函數式接口 (functional interface) 。

 

在需要使用到函數式接口的對象時 ,我們就可以提供一個 lambda 表達式 。

 

所以 ,與其把 lambda 表達式看做一個對象 ,不如認爲 lambda 表達式是一個函數 。

 

3. 方法引用

我們之前這樣實現了一個 lambda 表達式 :

 

ActionListener listener = event -> 
    System.out.println("現在的時間是 : " + new Date()"); 

這時就有人會說了 ,

 

“這個 lambda 表達式看着還是差那麼點意思啊 ,能不能再給力一點呢 ,讓這個表達式更加簡單 呢”

 

可以 ,這時候我們就要再介紹一位新朋友 ,先看看它長什麼樣子 :

 

Timer t = new Timer(1000, System.out::println) ;

 

表達式 System.out::println 是一個方法引用(method reference ) , 它等價於 lambda表達式 x 一> System.out.println(x)

 

再看一個例子 :

 

Arrays.sort(strings,String::conpareToIgnoreCase) ;

 

從這些例子可以看出 ,要用 ' :: ' 操作符分隔方法名與對象或類名 。

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

 

在前 2 種情況中 ,方法引用等價於提供方法參數的 lambda 表達式 。

 

前面已經提到 ,System.out::println 等價於 x -> System.out.println(x) 。類似地 , Math::pow 等價於 (x,y) -> Math.pow(x, y)

 

對於第 3 種情況 ,第 1 個參數會成爲方法的目標 。例如 , String::compareToIgnoreCase 等 同於(x, y) -> x.compareToIgnoreCase(y)

 

如果有多個同名的重栽方法 ,編譯器就會嘗試從上下文中找出你指的那一個方法 。 例如 ,Math.max 方法有兩個版本 ,一個用於整數 ,另一個用於 double 值 。選擇哪一個版本取決於 Math::max 轉換爲哪個函數式接口的方法參數 。類似於 lambda 表達式 ,方法引 用不能獨立存在 ,總是會轉換爲函數式接口的實例 。

 

可以在方法引用中使用 this 參數 。例如 ,this::equals 等同於x -> this.equals(x)

 

使用super 也是合法的 。下面的方法表達式 super::instanceMethod 使用 this 作爲目標,會調用給定方法的超類版本。

 

例如

class Greeter {
    public void greet() {
        System.out.println("Hello, world!"); 
    } 
}

class TimedCreeter extends Greeter {
    public void greet {
        Timer t = new Timer(1000, super::greet) ; 
        t.start();
    }
}

如果看到這還不夠盡興 ,可以看看 《Java 核心技術卷1》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章