利用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
初來乍到,有錯誤還望批評指正。