利用java8新特性實現類似javascript callback特性
Java8的新特性之一,就是首次引入了函數式編程Lambda表達式,按oracle的說法,是為了引導java向函數式編程的方向發展。
在JDK1.8中,多了一個包,java.util.function,這里主要用到了這個包下面的兩個接口:
Consumer<T> //Represents an operation that accepts a single input argument and returns no result. Function<T,R> //Represents a function that accepts one argument and produces a result.
要解釋清楚這個問題,首先得從lambda表達式說起
(x,y)->doSomethingWith(x,y);
這句話就是一個lambda表達式的例子;"->"是Java8新定義的一個操作符,操作符左邊代表lambda表達式接收的參數,這里它接收了兩個參數,x和y;表達式右邊是函數操作,也就是對這兩個變量執行某種操作。如x+y,x*y等。
簡單的解釋了一下java8的lambda表達式,接下來進入正題:
在java8中,function包下面的所有接口都描述了一種預定義的lambda表達式類型,換句話說,就是可以通過聲名接口類型的變量為lambda賦值,從而達到函數參數化的目的,這樣說可能比較抽象,看代碼
public class LambdaDemo { public static void adapter(Function<String, String> function) { String message = "Hello World"; System.out.println(function.apply(message)); } public static void main(String[] args) { Function<String, String> function1 = (str) -> { return str.toUpperCase(); }; Function<String, String> function2 = (str) -> { return str.toLowerCase(); }; adapter(function1); adapter(function2); } }
仔細體會上面的代碼,函數adaper這里表示對字符串進行某種適配并將它打印出來,而具體的適配方式是通過參數傳過來的,我們來看看運行結果:
HELLO WORLD hello world
和預期的完全一樣,如果你能看懂上面的代碼,我相信你已經基本明白了java8的函數式接口用法。
下面我針對上面提到的兩個接口做一下解釋:
所有的接口都是泛型定義的,泛型的作用在于類型推斷,也就是說你指定了lambda的類型,那么他接收的參數的類型就是確定的,編譯器就可以推斷lambda的類型。事實上,在“->”運算符左邊括號內的參數都是“匿名”的,你既無需考慮它們的引用,也無需事先聲名它們,它們只在當前lambda表達式內作用,并且類型已經確定。再深入思考一點,如果你熟悉接口重載,你可能覺得這和泛型一樣,是一塊語法糖,事實上并非如此,Oracle為了引導java向函數式編程的方向發展,放棄了簡單的接口重載,而是通過動態編譯實現的。
再說說這兩個接口的區別:
Consumer 中文譯作“消費者”,它通過接口下的accept方法,接收唯一的參數,并執行操作;參數和調用該方法的上下文是無關的,也就是說對變量執行的操作不影響原上下文中的變量;
Function 接口則可以通過調用apply方法返回一個值,從而供之后調用。
要解釋清楚這一問題,還得靠代碼:
public class ContextDemo { public static String transform(String str) { return str.concat(" World"); } public static void main(String[] args) { Function<String, String> function = (str) -> { return transform(str); }; Consumer<String> consumer = (str) -> { str = transform(str); }; String msg = "Hello"; System.out.println(msg); consumer.accept(msg); System.out.println(msg); msg = function.apply(msg); System.out.println(msg); } }
運行結果:
Hello Hello Hello World
可見,Consumer并沒有影響到它的上下文,它用的參數是變量的“副本”;而不是變量的指針。
接下來說說類似js中的callback();
對于一項功能,如果我們能夠提供多個參數,我們傾向于使用函數或者方法來實現;但是如果我們需要用到多個參數,由于函數至多只有一個返回值,所以此時采用方法的思想我們需要多個函數或者方法,這時最簡單的就是將方法傳過去,而不用返回,類似的場景在JS中非常常見
function doSomething(callback){ var a = 1; var b = 2; callback(a,b); } doSomething(function(a,b){ alert(a + b); });
jQuery Ajax方法中success場景下的data就是一個典型的callback,現在java也可以實現類似的效果,從而提高代碼重用率
public class CallbackDemo { public static void main(String[] args) { sayHello((msg) -> { System.out.println(msg); }); sayHello((msg) -> { System.out.println(msg.toUpperCase()); }); sayHello((msg) -> { System.out.println(msg.replaceAll("o", "0")); }); } public static void sayHello(Consumer<String> callback) { String msg = "Hello World"; callback.accept(msg); } }
運行結果如下:
Hello World HELLO WORLD Hell0 W0rld
初來乍到,有錯誤還望批評指正。