天天看點

三、java函數式程式設計-函數接口

作者:shadow武神

在前兩章學習了 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