java闭包和回调浅析

按照概念,闭包(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的闭包与回调机制,结合后面的内容会有更多意想不到的作用。

你可能感兴趣的:(java学习笔记)