Java中的回調(diào)函數(shù)

ioc-aop.png


    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)。

aop.png

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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,633評(píng)論 18 399
  • IOC是一種可以幫助我們解耦各業(yè)務(wù)對(duì)象間依賴關(guān)系的對(duì)象綁定方式。 一、注入方式(通常我們會(huì)說(shuō)IOC是通過(guò)DI來(lái)實(shí)現(xiàn)...
    遠(yuǎn)o_O閱讀 255評(píng)論 0 0
  • 一去十來(lái)里, 汝河水流急。 不當(dāng)釣魚(yú)漢, 只為景色迷。
    蕙蘭漱雪閱讀 198評(píng)論 1 1
  • 常建《題破山寺后禪院》 譯文 大清早我走進(jìn)這古老寺院,旭日初升映照著山上樹(shù)林。 竹林掩映小路通向幽深處,禪房前后花...
    甜蜜荔枝閱讀 582評(píng)論 0 5

友情鏈接更多精彩內(nèi)容