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》