
function foo(callback) {
callback();
}
function bar() {
// do something ...
}
foo(bar);
相信你一定看過(guò)上面這種形式的代碼,沒(méi)錯(cuò),這就是在動(dòng)態(tài)語(yǔ)言中被頻繁運(yùn)用到的回調(diào)函數(shù)。
C、C++和Pascal中也允許將函數(shù)作為指針傳遞
那么問(wèn)題來(lái)了,如何使用沒(méi)有函數(shù)類型的Java語(yǔ)言,寫(xiě)出一個(gè)回調(diào)函數(shù)呢?
Why Callback?
為什么要使用回調(diào)函數(shù)呢?或者換個(gè)方式提問(wèn),使用回調(diào)函數(shù)有什么好處呢?
事件回調(diào)
定義一個(gè)Person 的類,調(diào)用它的 greeting()方法。
let Person = (function() {
// 構(gòu)造
function Person(name) {
this.name = name;
}
Person.prototype.name;
Person.prototype.say = () => {
return "說(shuō):"
};
Person.prototype.greeting = () => {
let greets = "你好!";
console.log(this.name + this.say() + greets);
};
return Person;
}());
let ming = new Person("小明");
ming.greeting();
// 執(zhí)行結(jié)果
> 小明說(shuō):你好!
如果天氣會(huì)影響到小明的心情,那么也許打招呼的方式也會(huì)不同。我把greeting這個(gè)方法重寫(xiě)一下,讓一個(gè)函數(shù)傳入,看看會(huì)有什么不一樣的地方。
// override
Person.prototype.greeting = (greetingByWeather) => {
let greets = "你好!";
greetingByWeather(greets);
};
ming.greeting((greets) => {
console.log("今天是個(gè)大晴天!");
console.log(ming.name + ming.say + greets);
});
ming.greeting((greets) => {
console.log("今天下了小雨!");
console.log(ming.name + "沒(méi)有出門");
});
// 執(zhí)行結(jié)果
> 今天是個(gè)大晴天!
> 小明說(shuō):你好!
> 今天下了小雨!
> 小明沒(méi)有出門
OMG!很明顯,小雨天對(duì)小明來(lái)說(shuō)不太友好了,所以他選擇宅在家里。這次,兩個(gè)不同回調(diào)函數(shù)導(dǎo)致了這個(gè)打招呼的行為改變。
這種回調(diào)方式在JavaScript中被稱作同步回調(diào)
這種把自身的全部或部分邏輯交給回調(diào)函數(shù)處理的方式,可以讓寫(xiě)代碼的人擺脫繁瑣的if……else……代碼塊,帶來(lái)簡(jiǎn)潔、靈活、高效的書(shū)寫(xiě)體驗(yàn)。但是,這種方法也會(huì)帶來(lái)可讀性差等缺點(diǎn)。不過(guò),這個(gè)不在這篇文章的討論范圍中。
相同的對(duì)象,調(diào)用其相同的方法,參數(shù)也相同時(shí),但表現(xiàn)的行為卻不同。是不是讓你浮想連篇……這不就是Java中常說(shuō)的多態(tài)嘛!
代理回調(diào)
重新再定義一個(gè)Person的類,調(diào)用它的 greeting()方法。
class Person(object):
# 構(gòu)造函數(shù)
def __init__(self, name):
self.name = name;
def greeting(self):
print(self.name + self.say(), "你好")
def say(self):
return "說(shuō):"
ming = Person("小明")
ming.greeting()
# 執(zhí)行結(jié)果
>>> 小明說(shuō): 你好
這個(gè)時(shí)候,如果小明想要記下自己打招呼的時(shí)間,就有了
def greeting(self):
print("在", datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "時(shí):")
print(self.name + self.say(), "你好")
# 輸出
>>> 在 2018-07-12 17:06:38 時(shí):
>>> 小明說(shuō): 你好
但是,如果需要記錄 Person這個(gè)類中所有方法的執(zhí)行時(shí)間。是不是要在所有方法里添加這行代碼呢?
使用回調(diào)函數(shù)來(lái)處理這件事情,會(huì)有什么不同呢?
def record_time(func):
print("在", datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "時(shí):")
return func()
record_time(ming.greeting)
# 輸出
>>> 在 2018-07-12 17:19:54 時(shí):
>>> 小明說(shuō): 你好
這樣一來(lái),我們重復(fù)性的代碼能減少許多,我們使用record_time()作為一個(gè)代理,去調(diào)用我們真正的業(yè)務(wù)代碼。
Python給了這種代理函數(shù)一個(gè)好聽(tīng)的名字,叫做 裝飾器 ,并且賦予了它特殊的書(shū)寫(xiě)樣式。
現(xiàn)在我就來(lái)修改一下上述的代碼,讓它在調(diào)用時(shí)變得更加符合直覺(jué)。
def record_time(func):
def wrapper(*args, **kwargs):
print("在", datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "時(shí):")
return func(*args, **kwargs)
return wrapper
def greeting(self):
print(self.name + record_time_v2(self.say), "你好")
@record_time
def greeting_v2(self):
print(self.name + self.say_v2(), "你好")
ming.greeting = record_time(ming.greeting)
ming.greeting()
ming.greeting_v2()
把方法greeting_v2()想象成一張自左向右滾動(dòng)的紙帶,然后用剪刀在紙帶上任意位置裁開(kāi)一道口子,把我們所需要的東西織入紙帶。
以滿足我們?nèi)找嬖龆嗟淖儜B(tài)需求。
這些開(kāi)口,就被稱之為程序切面(Aspect),這種編程的思維被稱作面向切面編程(Aspect Oriented Programming)。

Java 8 的函數(shù)指針
接口重載
通過(guò)接口重載,Java中可以很容易地實(shí)現(xiàn)”多態(tài)“。
public interface MyInterface {
void greeting();
}
public class MyInterfaceImpl implements MyInterface {
@Override
void greeting() {
System.out.println("你好");
}
}
public static void main(String args[]) {
MyInterface i = new MyInterfaceImpl();
i.greeting();
}
接口MyInterface無(wú)法被實(shí)例化,但是被允許以參數(shù)的形式傳入方法中。在調(diào)用該方法時(shí),調(diào)用方不得不去實(shí)現(xiàn)該接口內(nèi)定義的方法。
Java中使用這種方法進(jìn)行回調(diào)的例子很多,最典型的就是Thread這個(gè)對(duì)象。
public static void main(String args[]){
new Thread(new Runnable() {
@Override
public void run() {
// your codes here
}
}).start(););
}
當(dāng)然,在Java程序規(guī)范中并不鼓勵(lì)以上這種寫(xiě)法。面向?qū)ο蟮挠^點(diǎn)傾向于抽象出一個(gè)具體的實(shí)現(xiàn)類,然后調(diào)用這個(gè)具體的實(shí)現(xiàn)類。(除非是用完之后就被使用者拋棄)
基于此,我們可以很容易的寫(xiě)出這種類似回調(diào)函數(shù)的東西。
interface GreetingByWeather {
void greeting(Person person);
}
class Person {
public String name;
public Person () {};
public Person (String name) {
this.name = name;
}
public void greeting (GreetingByWeather greetingByWeather) {
greetingByWeather.greeting(this);
}
public String say() {
return "說(shuō):";
}
}
public static void main(String args[]) {
Person ming = new Person("小明");
ming.greeting(new GreetingByWeather {
@Override
public void greeting(Person person) {
System.out.println("今天是個(gè)大晴天!");
System.out.print(person.name);
System.out.print(person.say());
System.out.println("你好!");
}
});
ming.greeting(new GreetingByWeather {
@Override
public void greeting(Person person) {
System.out.println("今天下起了雨!");
System.out.print(person.name);
System.out.println("沒(méi)有出門。");
}
});
}
// 運(yùn)行結(jié)果
今天是個(gè)大晴天!
小明說(shuō):你好!
今天下去了雨!
小明沒(méi)有出門。
通過(guò)上面的例子,我們發(fā)現(xiàn),接口GreetingByWeather定義了一個(gè)方法,傳入了一個(gè)Person參數(shù),返回了一個(gè)結(jié)果(void)。這也意味著,如果我們沒(méi)有對(duì)接口進(jìn)行聲明,那么我們也無(wú)法正常地調(diào)用該函數(shù)(廢話blabla……)
如果,能在業(yè)務(wù)調(diào)用的時(shí)候聲明參數(shù)和返回值的類型,該有多好啊。
函數(shù)接口
Java 8 帶來(lái)了許多新特性,諸如枚舉類、lambda表達(dá)式、接口默認(rèn)方法和函數(shù)接口等。其中,大部分都是基于原有的Java特性的增強(qiáng)和補(bǔ)充。而像lambda表達(dá)式之類的,更像是一種函數(shù)式編程的語(yǔ)法糖,它讓原有的函數(shù)式編程代碼更加簡(jiǎn)潔。
java.util.function包下,多了許多函數(shù)式的接口聲明,它們也可以被近似地看做是一種語(yǔ)法糖。
而事實(shí)上,Java8為了在函數(shù)式編程的方向上有所發(fā)展,放棄了簡(jiǎn)單的接口重載,而是通過(guò)動(dòng)態(tài)調(diào)用 **[invokeddynamic](https://stackoverflow.com/questions/30002380/why-are-java-8-lambdas-invoked-using-invokedynamic)** 來(lái)實(shí)現(xiàn)的。
下面以Function接口為例。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
public class Person {
...
public void greeting(Function<Person, Boolean> function) {
boolean result = function.apply(this);
// do sth with result ……
}
...
}
public static void main(String args[]) {
Person ming = new Person("小明");
ming.greeting(new Function<Person, String>() {
@Override
public String apply(Person person) {
System.out.println(person.name + person.say() + "你好");
return true;
}
});
}
上面的實(shí)現(xiàn)還可以輕易被lambda表達(dá)式替代。
ming.greeting(person -> {
System.out.println(person.name + "沒(méi)有出門");
return false;
});
Function接口,允許我們?cè)诰帉?xiě)具體業(yè)務(wù)代碼的同時(shí),聲明傳入的參數(shù)和返回值類型。而我們也不必花費(fèi)額外的開(kāi)銷去聲明一個(gè)接口。這種書(shū)寫(xiě)方式看上去和接口重載很像,但絕不一樣。
Java中的動(dòng)態(tài)代理
前面我們已經(jīng)提到過(guò)AOP編程。
而一提到AOP,就讓人聯(lián)想到Java中聲名顯赫的Spring框架。Spring帶來(lái)了管家式的編程體驗(yàn),幾乎接管了J2EE世界里的一切組件。在某種程度上,我們討論Java Web編程的時(shí)候,就是在討論Spring框架。
而在這里,我們脫離Spring框架,使用原生的Java代碼來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理。
定義接口和實(shí)現(xiàn)
public interface GreetingService {
void greeting(Person person);
}
public class GreetingServiceImpl implements GreetingService {
@Override
void greeting(Person person) {
System.out.println(person.name + person.say() + "你好!");
}
}
public class Person {
...
public void greeting(greetingService greetingService) {
greetingService.greeting(this);
}
...
}
自jdk1.3版本以來(lái),反射機(jī)制被引入Java體制內(nèi),它可以獲取到被編譯完成的類中之屬性、方法或注解等元素。
定義一個(gè)
InvocationHandler的實(shí)現(xiàn)類
private class GreetingIHImpl implements InvocationHandler {
private Object obj;
GreetingIHImpl(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("在 " + DateFormat.getDateTimeInstance().format(new Date()) + " 時(shí):");
Object reflectObj = method.invoke(obj, args);
return reflectObj;
}
}
最后,我們?cè)谡{(diào)用greetingService.greeting()方法的時(shí)候,將代理中的實(shí)現(xiàn)GreetingIHImpl織入到原有的接口中。
GreetingService greetingService = (GreetingService) Proxy.newProxyInstance(
GreetingService.class.getClassLoader(),
new Class[]{GreetingService.class},
new GreetingIHImpl(new GreetingServiceImpl())
);
...
ming.greeting(greetingService);
// 輸出
在 2018-07-12 17:19:54 時(shí):
小明說(shuō): 你好!
<c>受限于筆者自身水平,文章可能含有技術(shù)性或描述上的錯(cuò)誤。</c>
<c>歡迎指出問(wèn)題、提出問(wèn)題和制造問(wèn)題。</c>
原文地址:https://code.evink.me/2018/07/post/java8-callback-function/