1 |
|
相信你一定看过上面这种形式的代码,没错,这就是在动态语言中被频繁运用到的回调函数。
那么问题来了,如何使用没有函数类型的Java语言,写出一个回调函数呢?
Why Callback?
为什么要使用回调函数呢?或者换个方式提问,使用回调函数有什么好处呢?
事件回调
定义一个Person 的类,调用它的 greeting() 方法。
1 |
|
如果天气会影响到小明的心情,那么也许打招呼的方式也会不同。我把greeting这个方法重写一下,让一个函数传入,看看会有什么不一样的地方。
1 |
|
OMG!很明显,小雨天对小明来说不太友好了,所以他选择宅在家里。这次,两个不同回调函数导致了这个打招呼的行为改变。
这种把自身的全部或部分逻辑交给回调函数处理的方式,可以让写代码的人摆脱繁琐的if……else……代码块,带来简洁、灵活、高效的书写体验。但是,这种方法也会带来可读性差等缺点。不过,这个不在这篇文章的讨论范围中。
相同的对象,调用其相同的方法,参数也相同时,但表现的行为却不同。是不是让你浮想连篇……这不就是Java中常说的多态)嘛!
代理回调
重新再定义一个Person 的类,调用它的 greeting() 方法。
1 |
|
这个时候,如果小明想要记下自己打招呼的时间,就有了
1 |
|
但是,如果需要记录 Person这个类中所有方法的执行时间。是不是要在所有方法里添加这行代码呢?
使用回调函数来处理这件事情,会有什么不同呢?
1 |
|
这样一来,我们重复性的代码能减少许多,我们使用record_time()作为一个代理,去调用我们真正的业务代码。
Python给了这种代理函数一个好听的名字,叫做 装饰器 ,并且赋予了它特殊的书写样式。
现在我就来修改一下上述的代码,让它在调用时变得更加符合直觉。
1 |
|
把方法
以满足我们日益增多的变态需求。
这些开口,就被称之为程序切面(Aspect),这种编程的思维被称作面向切面编程(Aspect Oriented Programming)。
Java 8 的函数指针
接口重载
通过接口重载,Java中可以很容易地实现”多态“。
1 |
|
接口
Java中使用这种方法进行回调的例子很多,最典型的就是
1 |
|
当然,在Java程序规范中并不鼓励以上这种写法。面向对象的观点倾向于抽象出一个具体的实现类,然后调用这个具体的实现类。(除非是用完之后就被使用者抛弃)
基于此,我们可以很容易的写出这种类似回调函数的东西。
1 |
|
通过上面的例子,我们发现,接口
如果,能在业务调用的时候声明参数和返回值的类型,该有多好啊。
函数接口
Java 8 带来了许多新特性,诸如枚举类、lambda表达式、接口默认方法和函数接口等。其中,大部分都是基于原有的Java特性的增强和补充。而像lambda表达式之类的,更像是一种函数式编程的语法糖,它让原有的函数式编程代码更加简洁。
java.util.function包下,多了许多函数式的接口声明,它们也可以被近似地看做是一种语法糖。
下面以Function接口为例。
1 |
|
上面的实现还可以轻易被lambda表达式替代。
1 |
|
Function接口,允许我们在编写具体业务代码的同时,声明传入的参数和返回值类型。而我们也不必花费额外的开销去声明一个接口。这种书写方式看上去和接口重载很像,但绝不一样。
Java中的动态代理
前面我们已经提到过AOP编程。
而一提到AOP,就让人联想到Java中声名显赫的Spring框架。Spring带来了管家式的编程体验,几乎接管了J2EE世界里的一切组件。在某种程度上,我们讨论Java Web编程的时候,就是在讨论Spring框架。
而在这里,我们脱离Spring框架,使用原生的Java代码来实现动态代理。
定义接口和实现
1 |
|
自jdk1.3版本以来,反射机制被引入Java体制内,它可以获取到被编译完成的类中之属性、方法或注解等元素。
定义一个
InvocationHandler 的实现类
1 |
|
最后,我们在调用
1 |
|