025、内部类应用

Par @Martin dans le
Tags :

1. 多重继承

内部类最吸引的原因是: 每个内部类都能独立地继承/实现一个类/接口.

这样就为类提供了__”多重继承”__的可能.

class D {
}

class E {
}

class O extends D { // 外部类继承 D

    class I extends E { // 内部类继承 E
    }

    public E makeE() {
        return new I();
    }
}

public class TestDemo {
    static public void takesD(D d) {
    }

    static public void takesE(E e) {
    }

    public static void main(String[] args) {
        O o = new O();
        takesD(o);
        takesE(o.makeE());
    }
}

如上面的代码, 我们的类 O 本身继承了 D, 又可以通过 makeE() 方法返回一个继承 E 的对象, 这就相当于”多重继承”: 让类 O “拥有”了类 D 和 类 E 的特征.
如果你还有一个类需要被继承, 可以在类 O 里再添加一个内部类, 用它来继承那个类.

2. 闭包与回调

闭包貌似是在 Java8 中才被引进的, 但我在使用其他语言(Lua、Python)时, 早已接触过闭包了.

在 Lua 中, 可以这么解释闭包:
如果在一个内部函数里, 对外部作用域(但又不是在全局作用域)的变量进行引用, 那么内部函数就被认为是闭包(closure).

更官方的一点定义是:
闭包(closure)是一个可调用的对象, 它记录了一些信息, 这些信息来自于创建它的作用域.

通过这个定义, 可以看出, java 中的内部类就是一个面向对象的闭包, 因为它不仅包含其外部类对象(创建内部类的作用域)的信息, 还自动拥有一个__指向此外部类对象的引用__, 在此作用域内, 内部类有权操作外部类的所有成员.

而所谓的__回调__, 就是通过传递对象的一些信息, 然后在稍后的某个时刻允许这些信息调用其初始的对象, 在 C 中这一般通过传递对象的指针来实现, 而指针的弊端嘛…大家都懂的;

在 Java 中, 内部类提供的闭包功能是一个优良的替代方案, 它比指针灵活、安全.

interface Incrementable { // 一个计数器接口
    public void increment();
}

class Caller {
    private Incrementable callbackReference;

    Caller(Incrementable cbh) {
        callbackReference = cbh;
    }

    public void run() {
        callbackReference.increment(); // 通过引用回调 increment() 方法
    }
}

class Callee1 implements Incrementable { // 实现接口
    private int i = 0;

    public void increment() {
        System.out.println("一个计数器");
        i++;
        System.out.println(i);
    }
}

abstract class MyIncrement { // 一个计数器抽象类
    abstract public void increment();
}

class Callee2 extends MyIncrement {
    private int i = 10;

    public void increment() { // 重写
        System.out.println("另一个计数器");
        i++;
        System.out.println(i);
    }

    private class Closure implements Incrementable {
        public void increment() { // 实现, 提供一个外部类的 Hook
            Callee2.this.increment();
        }
    }

    public Incrementable getCallbackReference() { // 返回一个 Incrementable 引用, 任何人拿到这个引用也只能调用 increment() 方法
        return new Closure();
    }

    // 其它方法
}

public class TestDemo {

    public static void main(String[] args) {
        Callee1 c1 = new Callee1();
        Caller caller1 = new Caller(c1); // 设置回调
        caller1.run();

        Callee2 c2 = new Callee2();
        Caller caller2 = new Caller(c2.getCallbackReference()); // 设置回调
        caller2.run();
    }
}

Caller 的构造函数需要一个 Incrementable 的引用来作为参数, 然后在稍后的某个时刻, Caller 对象可以使用此引用回调 increment() 方法.

回调价值在于它的灵活性, 可以在运行时动态地决定需要调用什么方法(根据你传递的引用参数).

假设我们需要一个计数功能, Callee1 是一个简单的解决方法, 它单独地去实现 Incrementable 接口, 然后传给 Caller 的构造函数, 这没什么好说的.

主要看 Callee2, 它继承于 MyIncrement, 它也有一个 increment() 方法, 我们希望能够让 Caller 去回调这个方法, 但是可惜的是 Callee2 并不实现 Incrementable 接口, 所以不能传递给 Caller 的构造函数.

所幸, 我们还有内部类 Closure, 这个内部类实现了 Incrementable, 并提供一个其外部类 Callee2 的 “Hook”, 而且是一个安全的 “Hook”, 这样, 无论谁拿到 Closure 引用, 都只能调用 Callee2 的 increment() 方法, 而不能去操作 Callee2 的其它成员.

这也是为什么说内部类比指针更灵活, 安全的原因.
因为, 如果你得到了某个对象的指针, 相当于拥有了这个对象的完全控制权, 而内部类不一样, 它是可控的, 你可以在内部类中指定要 “Hook” 的成员, 例如刚才的例子, 我们就只 “Hook” 了 increment() 方法.