在前兩章學習了 Lambda 表達式的寫法和Stream的基本操作方法,可以囫囵吞棗簡單的寫寫代碼了。那麼不了解的地方就是,為什麼可以将Lambda 表達式作為參數傳遞?為什麼Lambda 表達式固定的括号裡不帶參數,或者帶參數?下面一步一步地進行講解。
在 Java 裡,所有方法參數都有固定的類型,如下代碼
public int add(int x, int y) {
return x + y;
}
// 調用方法
add(2, 3);
代碼中參數x和y都是int類型,函數調用時傳入兩個int數值就可以了,那麼函數調用傳入Lambda 表達式,如下代碼所示
new Thread(() -> System.out.println("函數式程式設計")).start();
代碼裡的Lambda 表達式是什麼類型呢?看一下java源代碼中Thread的定義
// Thread定義
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
// Runnable定義
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
代碼中Runnable是接口,該接口标志了@FunctionalInterface注解,可以看出@FunctionalInterface注解的接口和Lambda 表達式緊密聯系的,Runnable接口與Lambda 表達式寫法轉換如下所示
// Thread傳入Runnable接口的匿名内部類
new Thread(new Runnable() {
public void run() {
System.out.println("函數式程式設計");
}
}).start();
// 轉換成Lambda表達式步驟
// 1、Thread構造方法隻能傳入Runnable接口,是以匿名實作類new Runnable(){}省略
// 2、Runnable接口隻有一個接口run方法,是以public void run省略
// 3、run方法沒有參數,是以隻留下(),加上->分割符區分參數和代碼塊
// 轉成變成Lambda表達式如下
new Thread(() -> {System.out.println("函數式程式設計");}).start();
// 代碼塊裡面隻有一句代碼,是以省略代碼塊{}部分,最終變成
new Thread(() -> System.out.println("函數式程式設計")).start();
再看一下帶參數的例子,比如java源代碼Stream中的filter方法定義,代碼如下
// filter方法傳入Predicate接口
Stream<T> filter(Predicate<? super T> predicate);
// Predicate接口定義
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Predicate接口同樣标志了@FunctionalInterface注解,Predicate接口與Lambda 表達式寫法轉換如下所示
// filter方法傳入Predicate接口的匿名内部類
Stream.of("nodejs", "java", "python", "javascript")
.filter(new Predicate<String>() {
public boolean test(String s) {
return s.startsWith("java");
}
});
// 轉換成Lambda表達式步驟
// 1、filter方法隻能傳入Predicate接口,是以匿名實作類new Predicate<String>(){}省略
// 2、Predicate接口隻有一個接口test方法,是以public boolean test省略
// 3、test方法有參數,是以保留(String s),加上->分割符區分參數和代碼塊
// 轉成變成Lambda表達式如下
Stream.of("nodejs", "java", "python", "javascript")
.filter((String s) -> {
return s.startsWith("java");
}
);
// 代碼塊裡面隻有一句代碼,且傳回值也是同樣的代碼,可以省略代碼塊{}部分
Stream.of("nodejs", "java", "python", "javascript")
.filter((String s) -> s.startsWith("java"));
// 定義參數類型String可省略,并且隻有一個參數,()也可省略,但如果是多個參數就必須加上(),逗号分隔參數,最終變成如下代碼
Stream.of("nodejs", "java", "python", "javascript")
.filter(s -> s.startsWith("java"));
如果代碼使用了已經定義好的方法,且此方法引用僅僅涉及單一方法,Lambda表達式提供更快捷的寫法,如下代碼
Stream.of("nodejs", "java", "python", "javascript")
.filter(s -> s.isBlank());
// String::isBlank方法引用就是Lambda表達式s -> s.isBlank()的快捷寫法
Stream.of("nodejs", "java", "python", "javascript")
.filter(String::isBlank);
綜合上面Thread和Stream這兩個例子中,匿名内部類一步一步轉換成Lambda 表達式看出,@FunctionalInterface函數接口就是Lambda 表達式的類型,并且@FunctionalInterface注解的接口隻有一個可以實作的抽象方法。
下面列舉的是6個基本的函數接口,每個函數接口都有固定的用法
函數接口 | 抽象方法 | 接口使用描述 | 方法引用示例代碼 |
UnaryOperator<T> | T apply(T t) | 對象處理後傳回同類型對象 | String::toLowerCase |
BinaryOperator<T> | T apply(T t1, T t2) | 兩個同類型對象處理後變成一個對象 | Integer::sum |
Function<T, R> | R apply(T t) | 對象處理後變成另外一個對象 | Integer::toBinaryString |
Predicate<T> | boolean test(T t) | 對象判斷 | String::isEmpty |
Supplier<T> | T get() | 生成一個對象 | LocalDate::now |
Consumer<T> | void accept(T t) | 消費對象 | System.out::println |