按照概念,闭包(Closure)是一种能被调用的对象,它保存了创建它的作用域的信息。
我们先来看下面的例子:
//Programmer.java
interface inter{
void work();
}
public class Programmer{ //并没有实现接口inter
private String name;
public Programmer(){}
public Programmer(String name){
this.name = name;
}
...//此处省略了name属性的setter和getter方法
public void work(){
System.out.println(name+"在敲代码");
}
}
//Teacher.java
public class Teacher extends Programmer implements inter{
public void work(){
System.out.println(super.name+"在讲课");
}
}
//Test.java
public class Test{
public static void main(String[] args){
Programmer p = new Programmer("老朱");
p = new Teacher();
p.work();
}
}
此时输出的只能是:老朱在讲课。
但是实际上我们需要通过 t 来调用相关函数来输出:老朱在敲代码。
现在,我们考虑一种情况:
我们新建一个 TestOther.java文件,类TestOther继承类Programmer:
在类TestOther内部定义一个内部类(我们规定,该内部类只由该外部类所属,用private修饰),
现在接口该由谁来实现呢?
我们最后都是要到main函数中 new一个TestOther对象,然后利用对象来调用Programmer中的work()函数以及TestOther中的work()函数的。现在TestOther已经继承了Programmer类,就不能在TestOther中直接定义一个work()函数了,所以接口不能由该类来实现。那我们就直接定义一个teach()函数,功能与TestOther中的work()函数相同。
我们知道,非静态内部类的方法可以访问外部类的成员。
所以,如果我们定义一个Closure内部类,然后在该类的内部定义一个work()函数(接口由内部类来实现),函数体用来回调外部类的成员teach();
这样 ,在另一个.java文件TestAll.java的main函数之中,我们通过TestOther的对象,可以直接调用work()函数(继承过来的,也就是父类Programmer的work()函数),就会输出:**在敲代码;
现在,如果让TestOther中的teach()函数变为public函数,事情就简单多了,直接利用上面的对象来调用即可输出:**在讲课。
但是为了体现java编程的灵活性,(在不改变teach()函数的private属性的情况下也不得不这么做):
现在,非静态内部类Closure内部的work()函数也回调了teach()函数,而且,考虑到内部类是private修饰的,所以不能在外部类外部进行实例化内部类对象来调用它定义的接口inter的work()函数。
所以我们在外部类中再定义一个public函数getCallbackReference(),来返回new Closure();即可,那么根据向上转型的参数统一化,我们利用接口inter作为函数的返回值类型。
这样,我们就可以在TestAll.java文件的main函数中利用TestOther的对象来调用public函数getCallbackReference(),利用其返回值(实例化内部类对象)来调用非静态内部类的work()函数,这样通过回调teach()函数,就可以输出:**在讲课。
下面给出相关代码:
//TestOther.java
public class TestOther extends Programmer{
public TestOther(){}
public TestOther(String name){
super(name);
}
/*
定义一个private函数,功能也是输出:**在讲课
*/
private void teach(){
System.out.println(getName()+"在讲课");
}
/*
非静态内部类回调外部类成员实现work()方法,非静态内部类的作用仅仅是向客户类
提供过一个回调外部类的途径,但该类为private修饰,不能在外部直接实例化对象,
所以需要再定义下面的getCallbackReference()函数
*/
private class Closure implements inter{
public void work(){
teach();
}
}
/*
定义一个可以被外部调用的public函数,用向上转型的规范返回非静态内部类的实例化对象
*/
public inter getCallbackReference(){
return new Closure();
}
}
//TestAll.java
public class TestAll{
public static void main(String[] args){
TestOther tp = new TestOther(""老朱);
tp.work(); //直接调用TestOther类从Programmer类继承到的work()方法
//表面上调用的是Closure的work()方法,实际上是回调TestOther的teach()方法
tp.getCallbackReference().work();
}
}
实际上,java并不能显示地支持闭包,但是对于非静态内部类而言,它不仅记录了其外部类地详细信息,还保留了一个创建非静态内部类对象的引用,并且可以直接调用外部类的private成员,因此,可以把非静态内部类当成面向对象领域的闭包。
通过这种仿闭包的非静态内部类,可以很方便地实现回调功能,回调就是某个方法一旦获得了内部类对象的引用后,就可以在合适的时候反过来调用外部类实例的方法。所谓回调,就是允许客户类(main函数所在的类)通过内部类引用来调用其外部类的方法。这是一种非常灵活的功能。
下面给出另一个相似的例子:
interface inter{
void fun();
}
class A implements inter
{
private int i = 0;
public void fun()
{
i++;
System.out.println(i);
}
}
class B
{
public void fun()
{
System.out.println("B、fun()");
}
static void fB(B b)
{
b.fun();
}
}
class C extends B{
private int i = 0;
public void fun()
{
super.fun();
i++;
System.out.println(i);
}
private class Closure implements inter
{
public void fun()
{
C.this.fun();
}
}
public inter getCallbackReference()
{
return new Closure();
}
}
class M
{
private inter in;
M(inter i){
in = i;
}
void funM(){
in.fun();
}
}
public class TestDemo {
public static void main(String[] args){
A a = new A();
C c = new C();
B.fB(c);
M m1 = new M(a);
M m2 = new M(c.getCallbackReference());
m1.funM();
m1.funM();
m2.funM();
m2.funM();
}
}
输出结果是:
B、fun()
1
1
2
B、fun()
2
B、fun()
3
首先,A简单的实现了接口inter与相关方法,在这里起到一个对比的作用而已。
然后定义一个B类同样实现了一个fun()方法但是这个与接口中的increment()没有任何关系,因为这个类自己实现的,并没有实现这个接口,而静态方法fB()也只是为了测试一下B类自己的fun()方法。
而C类继承自B类。同样写了一个fun()方法,覆盖了父类的fun()方法,但是函数内部还是调用了父类的fun()方法。
接着,在C类中定义了一个private修饰的非静态内部类Closure(也就是闭包的具体实现了)。
内部类实现了接口inter并且直接调用外部类的方法作为具体的实现。
内部类实现inter接口很关键,这样就给外部留下了一个通道,能够接受这个内部类。
最后类C的后面留下了一个钩子,即getCallbackReference()方法,它返回一个内部类的对象,实现了内部与外部的链接,同时有保证了内部类的安全,因为只有Callee2的对象可以访问与调用这个内部类的方法,而其他的类都无权访问,即使是基类接口对象。
而后面的定义的M类,起到的是唤醒作用,利用有参构造函数,通过接受不同的接口对象传入形参,实现不同的操作(向上转型规范)。
但还有一个作用是等待接受一个内部类对象,来产生回调,因为一旦传入的是具有内部类的类C实例化对象调用的getCallbackReference()函数,就可以实现回调作用。现在大家再回头看一下输出就能够明白了。
假装你回头看了,在main()方法中,首先是创建对象与声明,然后是调用了B类的静态方法fB(),(B是C的父类)传入的是一个C的实例化对象,此时无法触发回调,只是正常的输出(看起来很像回调,实际上只是执行了类B的静态成员fB()而已)
然后,在对C的初始化时传入的是一个Closure对象从而产生了回调。
以上就是java的闭包与回调机制,结合后面的内容会有更多意想不到的作用。