本文適用於想要了解Java8 Function接口編程及閉包表達式的筒鞋。
概述###
在實際開發中,常常遇到使用模板模式的場景: 主體流程是不變的,變的只是其中要調用的具體方法。 其特征是:
BeginTodo ---> Something different to do ---> others todo ---> End
其中BeginTodo ,others todo,End 都是不變的,只有 Something different to do 是根據業務變化的。 如果采用 Java 來實現,通常要為 Something different to do 定義回調接口 Callback , 然后在主體流程中調用這個回調接口,而在實際業務中,創建 Callback 的實現類傳入進去。
Java8 Function 以及閉包對此提供了更為簡潔方便的語言支持。 Function 是Java8對函數的抽象,用於描述和接收任何單參數單返回的函數,類似於 Callback 的作用; 而在使用的時候,只要將方法引用或 lambda 表達式傳給 Function 即可。
代碼示例###
舉例來說,要編寫一個通用方法,實現多次執行同一個測試方法,統計失敗次數。
Java-Function####
package com.xxx.trade;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.junit.Test;
import java.util.function.Function;
/**
* Created by shuqin on 16/11/15.
*/
public class GeneralExecTestMoreTimes extends TestCase {
public void execMoreTimes(Function f) {
execMoreTimes(f, new Object());
}
public void execMoreTimes(Function f, int num) {
execMoreTimes(f, null, num);
}
public void execMoreTimes(Function f, Object t) {
execMoreTimes(f, t, 20);
}
public void execMoreTimes(Function f, Object t, int num) {
int failed = 0;
for (int i = 0; i < num; i++) {
try {
f.apply(t);
} catch (AssertionFailedError afe) {
failed += 1;
} catch (Exception ex) {
failed += 1;
}
}
System.out.println("----- failed: " + failed + " -----");
assertEquals(0, failed);
}
@Test
public void testExecMoreTimes() {
execMoreTimes((o) -> {
System.out.println("haha");
return 1;
}, 5);
}
}
這里 execMoreTimes 方法接收一個函數式接口 Function 以及將應用於的參數 t 。 那么什么可以傳給 Function 接口呢? 可以是任何單參數單返回值的函數,或者單參數單返回值的 lambda 表達式。例如代碼中的 (o) -> { // some codes } 。
Groovy閉包####
如果使用 Groovy 編寫閉包,會更簡潔: 只要定義一個由大括號 {} 包圍的代碼塊,並賦給一個變量即可(如下面的test1),甚至可以直接在函數里調用一個由大括號包圍的代碼塊參數(如下面的test2)。 這樣, 使用代碼塊或函數就會更加直接、靈活、自由,而不會受制於語法,也不需要定義一堆接口了。
package com.xxx.trade
import org.junit.Test
/**
* Created by shuqin on 16/11/15.
*/
class GeneralExecTestMoreTimesTest extends GeneralExecTestMoreTimes {
@Test
public void test1() {
def closure = {
System.out.println("here is test")
}
execMoreTimes(closure)
}
@Test
public void test2() {
execMoreTimes({
throw new Exception("throw exception in test")
})
}
}
應用場景####
函數接口可以用於任何使用回調接口的場景,一個典型的應用場景是批量處理。 比如有三個小組對同一個列表進行處理,一個用於狀態同步,一個用於退款,一個用於取消。 那么,可以實現一個批量處理的通用函數,然后調用三個小組的自定義函數即可。
package zzz.study.utils;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
/**
* Created by shuqin on 17/1/19.
*/
public class BatchProcessUtil {
public static String batchProcessOrders(Consumer<String> processFunction, List<String> orders) {
StringBuilder result = new StringBuilder();
for (String orderNo: orders) {
String orderNoTrimed = orderNo.trim();
try {
processFunction.accept(orderNo);
result.append(orderNoTrimed + " OK , 請稍后查看!\n");
} catch (Exception e) {
result.append(orderNoTrimed + " Failed, 請稍后重試!\n");
}
}
return result.toString();
}
public static void sync(String orderNo) {
System.out.println("sync order state " + orderNo);
}
public static void refund(String orderNo) {
System.out.println("refund for " + orderNo);
}
public static void cancel(String orderNo) {
System.out.println("cancel " + orderNo);
}
public static void main(String[] args) {
List<String> orders = Arrays.asList(new String[] {"E001", "E002", "E003"});
batchProcessOrders((orderNo) -> sync(orderNo), orders);
batchProcessOrders((orderNo) -> refund(orderNo), orders);
batchProcessOrders((orderNo) -> cancel(orderNo), orders);
}
}
輸出是
sync order state E001
sync order state E002
sync order state E003
refund for E001
refund for E002
refund for E003
cancel E001
cancel E002
cancel E003
常用函數接口###
常用函數接口主要有:
- Consumer
(接收單參數無返回值的函數或lambda表達式), 方法是 void accept(T t); - BiConsumer<T, U> (接收雙參數無返回值的函數或 lambda表達式),方法是 void accept(T t, U u) ;
- Function<T, R> (接收單參數有返回值的函數或lambda表達式), 方法是 R apply(T t);
- BiFunction<T, U, R> (接收雙參數有返回值的函數或lambda表達式),方法是 R apply(T t, U u);
- Predicate
(接收單參數返回布爾值的函數或lambda表達式),方法是 boolean test(T t); - Supplier
(無參數返回值的函數或 lambda), 方法是 T get(); - 接受原子類型參數的函數接口,這里不一一列舉了。可參考 java8 package java.util.function;
小結###
為什么要使用 Function 以及閉包呢?
- 在語法上比定義回調接口、創建匿名類更加簡潔;
- 嘗試使用新的語言特性,理解多樣化的編程思想,提升編程表達能力。
