起因
最近来到了支付宝杭州做Java研发的实习,进组后马上就去了一个项目组,开始接触项目代码。一周做下来感觉收获还是挺大的,见到了很多新奇的模式,实现等等。不过确实也挺辛苦的,很少能挤出时间来学习学习新知识。周四的时候今秋给我和虎二科普了下关于Callback的Java实现方式。以前用CPP或者函数式的时候,都没有在意过这一块。CPP都是直接函数指针,粗暴地解决。函数式都可以把函数作为参数,也不存在什么问题。到了Java的时候,必须要完全用面向对象的问题去解决这个问题,还是遇到了一些障碍的。今晚利用实习结束的时间,忙里偷忙地学习了一下这方面的内容。
从头谈起
Java Interface
首先,需要讨论下Java Interface。接口是C++中没有的概念,与之前学的Scala中的trait有些类似。从语言语义的角度,Interface具有类型的功能,跟类是有些相像的,但同时接口不能包含实现,其中的方法都是抽象方法。同时还不能有自己的非静态成员变量。因此,感觉在语义上,接口是跟类等价的东西。但两个东西还是有一些不同的。
就我今晚的尝试来看,最大的不同,是在实例化的时候。比如我们定义如下的一个接口:
interface Index {
public void a();
}
在InterfaceTest类中,我们实例化两个Index接口类型的变量:
class InterfaceTest {
public static void main(String[] args) {
Index i = new Index() {
@Override
public void a() {
System.out.println("end back!!!");
}
};
Index j = new Index() {
@Override
public void a() {
System.out.println("end back!!!");
}
};
}
}
在编译的时候,InterfaceTest会编译产生三个字节码文件,分别叫做InterfaceTest,InterfaceTest$1,InterfaceTest$2。用javap去看他们的编码,会发现InterfaceTest是这样的:
class TestInterfaceTest extends java.lang.Object{
TestInterfaceTest();
Code:
0: aload_0
1: invokespecial #1;
4: return
public static void main(java.lang.String[]);
Code:
0: new #2; //class TestInterfaceTest$1
3: dup
4: invokespecial #3;
7: astore_1
8: new #4; //class TestInterfaceTest$2
11: dup
12: invokespecial #5; //Method TestInterfaceTest$2."<init>":()V
15: astore_2
16: return
}
而InterfaceTest$1和Interface$2内容是基本一致的(除了类名):
final class TestInterfaceTest$2 extends java.lang.Object implements Index{
TestInterfaceTest$2();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public void a();
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String end back!!!
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
其实可以看出两个问题,一个是,每次接口被new构造出来的时候,都会产生一个final class,这个class会继承接口。所以在字节码层,实例化接口其实最后还是用实例化实现接口的类来实现的。这个不是运行时的行为,是在编译的时候就确定了的。另一个问题,就是在我们的例子中,两次对接口的实例化其实是一模一样的,但Java还是会产生两个final class。这说明Java没办法知道两个实例化操作中的接口实现是否是等价的,这也符合我的预期。如果Java可以从语义的角度判断等价,那就有点黑科技了。
Callback
Callback就是回调,举例说明这个过程,比如A调用了B的b方法,那b方法如果包含对A中实现的逻辑的调用,这个过程就被称作回调。Leap Motion的Javascript API就是基于这样的回调的,每当Leap Motion的控制器拿到一帧手部的运动信息,就会回调以参数形式传给它的函数,函数中可以封装基于手部运动信息的一些操作,比如根据手的位置更新画布等等。用函数式的语言去描述,大概就是:
func a() {
// some operations
b(lambda() {
// callback func
})
}
func b(f: void -> Unit) {
//some operations
f();
// some operations
}
逻辑就是这样的。
Callback in Java
用Interface,我们可以比较简单的实现Callback:
interface CallBackUnit {
public void callbackFunc();
}
class CallBacker {
public void b(CallBackUnit callBackUnit) {
// some operations
callBackUnit.callbackFunc();
// some operations
}
}
class CallBacked {
public void a() {
CallBacker callBacker = new CallBacker();
callBacker.b(new CallBackUnit() {
@Override
public void callbackFunc() {
System.out.println("end back!!!");
}
});
}
}
当然,Java8似乎也有了匿名函数等等一些函数式的特性,有了这些实现CallBack机制就更简单了。