十四、内部类详解(下)

为什么要使用内部类

一般情况,内部类是一个接口或者类的实现,并且可以作为外围类的一个窗口,即通过内部类对象提供的接口方法操作外围类私有的成员。

如果只是需要实现一个接口,那么为什么不直接用外围类实现该接口,而需要使用内部类呢?

这个问题其实说明了我们在选择的时候,是选择直接实现接口,还是使用内部类,实际情况下,如果通过直接实现接口比较方便,那么就不用内部类

现在我们只考虑内部类实现方式,这个方式有什么特点呢?使用内部类实现接口或继承类,可以做到多重继承

下面看一个简单的例子:

interface A {}
interface B {}

class X implements A, B {
    
}

class Y implement A {
    B getB() {
        return new B() {};//匿名内部类
    }
}
public class test {
    static void takeA(A a) {
        
    }
    static void takeB(B b) {
        
    }
    
    public static void main(String [] args) {
        X x = new X();
        Y y = new Y();
        
        takeA(x);
        takeA(y);//y本身实现了接口A
        
        takeB(x);
        takeB(y.getB());//通过方法获得类B的内部类对象
    }
}

上述代码中,使用内部类和直接实现两个接口,语法上都过关,实际开发中选择哪种方式应当以实际业务为主。比如:实际业务需要一个类继承多个类,那么就只能以内部类的方式来实现了。

内部类多重继承的特点:

  1. 一个类的内部类,可以创建多个对象,每个对象都有独立的信息并且可以访问外围类的私有成员
  2. 在单个类中,可以存在多个内部类以不同的方式实现同一个接口或继承同一个类
  3. 创建内部类对象的时刻不依赖外围类对象的创建
  4. 使用内部类实现接口或继承对象,不存在’is a’这种复杂的关系

闭包与回调

闭包是指一个可调用的对象,它包含一些信息,这些信息是来自创建时的作用域。所以内部类实际上是一个面向对象的闭包,因为它不仅拥有外部类对象的信息,还拥有对外部类对象的引用。

interface Incrementable {
    void increment();
}

class callee1 implements Incrementable {
    private int i=0;
    
    public void increment() {
        i++;
        System.out.println(i);
    }
}

class MyIncrement {
    public void increment() {
        System.out.println("其他操作");
    }
    
    static void f(MyIncrement m) {
        m.increment();
    }
}

//此类继承 MyIncrement 类,并且通过内部类实现接口 Incrementable
class callee2 extends MyIncrement {
    private int i=0;
    
    //重写父类的方法,手动调用父类的方法
    public void increment() {
        super.increment();
        i++;
        System.out.println(i);
    }
    
    //内部类实现 Incrementable 接口的 increment() 方法
    public class Closure implements Incrementable {
        public void increment() {
            callee2.this.increment();
        }
    }
    
    //得到内部类对象的方法
    Incrementable getCallBackRef() {
        return new Closure();
    }
}

class caller {
    private Incrementable ref;
    caller(Incrementable ref) {
        this.ref = ref;
    }
    void go() {
        ref.increment();
    }
}

public class test1 {
    public static void main(String [] args) {
        callee1 c1 = new callee1();//实现 Incrementable 接口
        callee2 c2 = new callee2();//继承 MyIncrement 类,并且通过内部类实现接口 Incrementable
        
        MyIncrement.f(c2);
        
        caller caller1 = new caller(c1); // caller类接收一个Incrementable对象
        caller caller2 = new caller(c2.getCallBackRef());
        
        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
    }
}
/*Output:
其他操作
1
1
2
其他操作
2
其他操作
3
*/

上述代码中,callee1类简单的实现了接口Incrementable。而callee2继承了类MyIncrement,其中已经存在一个与接口Incrementable中同名的方法,并且两者毫不相干,所以只能通过内部类来实现这个接口。

也就是说在一个操作类caller中,只接受接口Incrementable的类,那么要想使得callee2类也能够被操作,只需要在callee2类中创建一个内部类并实现接口Incrementable,内部类对象作为一个钩子,可以让操作类间接操作钩子所在的外围类。

这样实现的好处是这个钩子受接口的限制,只能使用接口的方法。如果以指针的形式交给操作类操作的话,可以对原类做任何操作,是不够安全的。

控制框架中的内部类

控制框架是指用于解决响应事件的需求,Java Swing 库就是一个典型的控制框架。

举个栗子,现在有一个简单控制框架,在事件就绪的时候,这里指倒计时结束时,触发事件,要触发的事件可以是实现一些操作,我们这里简化为执行action()方法。

首先定义事件类:

public abstrat class Event {
    public long startTime;
    protected final long waitTime;
    
    public Event(long waitTime) {
        this.waitTime = waitTime;
        start();
    }
    
    public start() {
        startTime = System.nanoTime() + waitTime;
    }
    
    public boolean ready() {
        return System.nanoTime() >= startTime;
    }
    
    public abstract action();
}

创建事件Event时,指定多少时间后触发,start()方法可以使事件触发后重新开始计时,再触发。

下面实现用于管理事件、触发事件的一个简单控制框架。事件统一被保存于一个列表中。

public class Controller {
    private List<Event> eventList = new ArrayList<Event>();
    
    public void addEvent(Event e) {
        eventList.add(e);
    }
    
    public void run() {
        while(eventList.size() > 0) {
            for(Event e : eventList) {
                if(e.ready()) {
                    System.out.println(e);
                    e.action();//触发事件
                    eventList.remove(e);//从列表中移除已经触发过的事件
                }
            }
        }
    }
}

到目前为止,控制框架不变的部分已经实现完成,但是现在还不知道那些事件触发时到底做了什么,这就是将变化的事物与不变的事物相互分离。变化的事物就是不同的Event子类具有不同的行为。

内部类要做的是:

  1. 控制框架的完整实现是一个单个类,从而使得细节全部封装起来,这个类可能需要很多不同的action,这就需要内部类来逐个实现了。
  2. 内部类可以很容易访问外部类的成员,使得整体代码非常简洁。

接下来考虑实现一个温室调节系统,即可以调节温度、湿度、灯光以及重启系统功能。考虑到这个温室调节系统是一个整体,但其内部所具有的事件有多个,因此内部的事件以内部类形式实现。

//继承自 Controller 类
public class GreenhouseControls extends Controller {
    private boolean light = false;//表示灯光开关
    private int humidity = 50;//表示湿度
    private int temperature = 37;//表示温度
    
    public class LightOn extends Event {
        public LightOn(long waitTime) {
            super(waitTime);
        }
        
        public void action() {
            light = true;
        }
        public String toString() {
            return "开灯";
        }
    }
    
    public class LightOff extends Event {
        public LightOff(long waitTime) {
            super(waitTime);
        }
        
        public void action() {
            light = false;
        }
        public String toString() {
            return "关灯";
        }
    }
    
    public class humidityUp extends Event {
        public humidityUp(long waitTime) {
            super(waitTime);
        }
        
        public void action() {
            humidity++;
        }
        public String toString() {
            return "湿度调高";
        }
    }
    
    public class humidityDown extends Event {
        public humidityDown(long waitTime) {
            super(waitTime);
        }
        
        public void action() {
            humidity--;
        }
        public String toString() {
            return "湿度调低";
        }
    }
    
    public class temperatureUp extends Event {
        public temperatureUp(long waitTime) {
            super(waitTime);
        }
        
        public void action() {
            temperature++;
        }
        public String toString() {
            return "温度调高";
        }
    }
    
    public class temperatureDown extends Event {
        public temperatureDown(long waitTime) {
            super(waitTime);
        }
        
        public void action() {
            temperature--;
        }
        public String toString() {
            return "温度调低";
        }
    }
    
    public class restart extends Event {
        private Event[] eventList;
        
        public restart(long waitTime, Event[] eventList) {
            super(waitTime);
            this.eventList = eventList;
            for(Event e : eventList) {// 将事件组放入到控制器的事件队列中
                addEvent(e);
            }
        }
        
        public void action() {
            for(Event e : eventList) {
                e.start();
                addEvent(e);
            }
            start();
            addEvent(this);//将重启事件本身也放进控制器队列,也就是定时重启
        }
        public String toString() {
            return "重启系统";
        }
    }
}

下面,我们来使用这个温室调节系统:

public class GreenHouseController {
    public static void main(String [] args) {
        GreenhouseControls ghc = new GreenhouseControls();
        
        ghc.addEvent(ghc.new humidityDown(900));
        
        Event[] eventList = {
            ghc.new LightOn(200);
            ghc.new LightOff(1000);
            ghc.new temperatureDown(2000);
            ghc.new temperatureUp(600);
        }
        
        ghc.addEvent(ghc.new restart(100, eventList));
        
        ghc.run();
    }
}

上述例子更加深刻的显示了内部类的使用价值。

内部类的继承与重写

内部类是可以被继承的,但是由于成员内部类对象都有一个外部类对象的引用,因此需要通过特殊语法来处理这个特性:

class outer {
    class inner {}
}

class test extends outer.inner {
    test(outer out) {
        out.super();
    }
}

在内部类的子类的构造器中,需要显示调用外部类的构造,使得该子类能够正确初始化。

对于重写,实际上父类与子类具有相同的内部类时,这两个内部类并没有关联,当然也不具有多态的特征了。

内部类标识

内部类编译后也会产生一个.class文件,命名规则是外围类名称加上$再加上内部类名称,匿名类则只有数字作为标识。局部内部类由于作用域比较小,可能存在同名类的情况,因此为了区分,局部内部类产生的.class文件会在$和内部类名之间标记数字。

interface Find{}

public class Out{
    public class Inner1{
    }

    public class Inner2{
    }

    public static class Inner3{
    }
    
    public void method(){
        class FindImpl implements Find{
        }
    }

    public void method2(){
        class FindImpl implements Find{
        }
    }

    public void method(){
        new Find(){
        };
    }

    public void method2(){
        new Find(){
        };
    }
}

上例代码中,产生的.class文件分别是:

  1. Out.class
  2. Out$Inner1.class
  3. Out$Inner2.class
  4. Out$Inner3.class
  5. Find.class
  6. Out$1FindImpl.class
  7. Out$2FindImpl.class
  8. Out$1.class
  9. Out$2.class
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值