内部类之二_高级篇

1. 嵌套类

如果不需要内部类与外围类对象之间有联系,那么可以将内部类声明为static–这就是嵌套类。
当内部类是static时就意味着:
1)要创建嵌套类的对象,并不需要外围类对象。
2)不能从嵌套类的对象中访问非静态的外围类对象。
3)普通内部类不能有static数据和字段。

public class parcel11 {

    private static class ParcelContents implements Contents{
        private int i = 11;
        public int value() {return i;}      
    }
    protected static class ParcelDestination implements Destination{
        private String label;
        private ParcelDestination(String whereto){label = whereto;}
        @Override
        public String ReadLable() { return label;}  
        public static void f(){}
        static int x = 10;
        static class AnotherLevel{
            public static void f(){}
            static int x = 10;
        }
    }
    public static Destination destination(){return new ParcelDestination("java");}
    public static Contents constents(){return new ParcelContents();}
    public static void main(String[] args) {
        Contents c = constents();
        Destination d = destination();
    }
}

在main函数中没有定义parcel11对象的必要,而是使用static方法返回Contents和Destination对象的引用。嵌套类不能使用this来连接到外部类的引用,这样这个内部类就像一个static方法。

1.1 接口内部的类

一般在接口中不能放置任何代码,如果嵌套类在接口的内部会自动转换为public和static。只是将嵌套类放置接口的命名空间内,所以不违反接口的规则。甚至可以在内部类中实现其外围接口。如:

public interface  ClassInInterface {
    void howdy();
    class Test implements ClassInInterface{
        @Override
        public void howdy() {
            // TODO Auto-generated method stub
            System.out.println("java");
        }
        public static void main(String[] args){
            new Test().howdy();
        }       
    }
}

如果你想要创建某些公共代码,使得他能被某个接口的所有不同实现所公用,那么使用内部类就很方便。
嵌套类还可以用来测试代码

public class TestBed {
    public void f(){System.out.println("f()");}
    public static class Tester{
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            TestBed t = new TestBed();
            t.f();
        }   
    }
}
//output:
f()

这样会生成一个独立的类TestBed$Tester,发布这个程序的时候删除这个类就行了。

1.2 从多层嵌套类中访问外部类的成员

一个内部类嵌套多少层并不重要,他能透明的访问所有它所嵌入的外围类的成员。

class MNA{
    private void f(){}
    class A{
        private void g(){}
        public class B{
            void h(){
                g();
                f();
            }
        }
    }   
}
public class MultiNestAccess {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnaab = mnaa.new B();
        mnaab.h();
    }
}

2.为什么需要内部类

内部类最吸引人的原因是:每个内部类都能独立的继承一个接口的实现,所以无论外围类是否已经继承某个接口的实现,对内部类没有影响。
内部类允许继承多个没接口类型(类或者抽象类)

interface A{}
interface B{}
class X implements A,B{}
class Y implements A{
    B getB(){
        return new B(){};
    }
}
public class MultiInterface {
    static void taskA(A a){}
    static void taskB(B b){}
    public static void main(String[] args) {
        // TODO Auto-generated method stub
            X x = new X();
            Y y = new Y();
            taskA(x);
            taskA(y);
            taskB(x);
            taskB(y.getB());
    }
}

这里看到了使用单一类和使用内部类的两种方式,看上去并没有什么不同,都能实现。

class D{}
abstract class E{}
class Z extends D{
    E getE(){return new E(){};}
}
public class MultiImplementation {
    static void tasksD(D d){}
    static void tasksE(E d){}
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Z z = new Z();
        tasksD(z);
        tasksE(z.getE());
    }
}

这样就实现了多继承。
使用内部类还可获得其他一些特性:
1)内部类可有有多个实例,每个实例都有自己的状态信息,与外围类对象的信息相独立。
2)在单个外围类中,可以让内部类以不同的方式实现同一个接口,或继承同一个类。稍后介绍。
3)创建内部类的对象时刻并不需要外围类对象的创建。
4)内部类没有“is-a”关系,他是独立的实体。

2.1 闭包与回调

闭包:是一个可以调用的对象,它记录了一些信息,这些信息来源于创建它的作用域。内部类就是面向对象的闭包,它包含外围了对象的信息和对外围类对象的应用,内部类有权操作所以成员包括private成员。
通过回调,对象能携带一些信息,这些信息允许在稍后的某个时刻调用初始的对象。
闭包示例:

package inner_class;

import static net.lijun.util.Print.*;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;

interface Incrementable{
    void increment();
}
//这里只是实现了Incrementable
class Callee1 implements Incrementable{
    private int i = 0;
    public void increment() {
        i++;
        println(i);
    }
}
class MyIncrement{
    public void increment(){println("Other operation");}
    static void f(MyIncrement mi){mi.increment();}
}
//如果你的类需要实现Incrementable接口就必须使用内部类
class Callee2 extends MyIncrement{
    private int i = 0;
    public void increment(){
        super.increment();
        i++;
        println(i);
    }
    private class Closure implements Incrementable{
        public void increment() {
            Callee2.this.increment();
        }

    }
    Incrementable getCallbackReference(){return new Closure();}
}
class Caller{
    private Incrementable callbackReference;
    Caller(Incrementable cbh){callbackReference = cbh;}
    void go(){callbackReference.increment();}
}
public class Callbacks {    
    public static void main(String[] args) {
        Callee1 c1 = new Callee1();
        Callee2 c2 = new Callee2();
        MyIncrement.f(c2);
        Caller caller1 = new Caller(c1);
        Caller caller2 = new Caller(c2.getCallbackReference());
        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
    }

}

这个例子解释了外围类实现一个接口与内部类实现一个接口的区别,Calee1是简单的解决方式。Calee2继承自‘MyIncrement’后者有个完全不同的’increment’,并且与Incrementable接口期望的方法increment完全不相干,所以Calee2继承了MyIncrement就不能为了Incrementable的用途而覆盖了increment方法,于是只能用内部类独立的实现Incrementable。
内部类Closure实现了Incrementable,以提供返回Callee2的一个钩子。
Caller的构造器需要一个Incrementable的应用作为参数,然后在以后的某个时刻,Caller可以回调Calee类。

2.2内部类与控制框架

应用程序框架是被设计用来解决某类特定问题的一个类或者一组类。
控制框架是特殊的应用程序框架,它用来解决相应事件的需求。在GUI中用的比较多。
首先接口描述了要控制的事件,因为默认的行为是基于时间去执行控制的,所以使用抽象类代替接口。
例:

public abstract class Event {
    private long eventTime;
    protected final long delayTime;
    public Event(long delayTime){
        this.delayTime = delayTime;
        start();
    }
    public void start(){
        eventTime = System.nanoTime()+delayTime;
    }
    public boolean ready(){
        return System.nanoTime()>=eventTime;
    }
    public abstract void action();
}

当运行Event时随即运行start()方法,那么就可以捕获当前时间加上延时时间,生成触发时间,
ready()告诉你何时可以运行action()方法,
下面的文件包含了一个用来管理并触发事件的实际控制框架。Event对象被保存在List中.

import java.util.*;

public class Contorller {
    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:new ArrayList<Event>(eventlist)){
                if(e.ready()){
                    System.out.println(e);
                    e.action();
                    eventlist.remove(e);
                }
            }
        }
    }
}

run()方法循环遍历eventlist寻找就绪的ready,要运行的对象。对每一个就绪的事件,使用toString打印对象,调用其action方法,然后冲队列中移除。
到现在你并不知道Event的action方法是什么。这就是这个设计的关键所在。使变化的事物与不变的事物分离开来。各种不同的Event所具有的不同行为,而你通过创建Event表现这些不同的行为。
这就是内部类要做的事情:
1)控制框架的完整实现是由单个类创建的,从而使实现的细节被封闭了起来。内部类用来表示解决问题所必须的各种不同的action()。
2)内部类可以访问外围类的任意成员,所以可以避免这种实现变得笨拙。
下面是一个温室系统的例子:
使用内部类可以在单一的类里面产生对同一基类Event的多种导出版本。

package inner_class;

public class GreenhouseControls extends Contorller {
    private boolean light = false;
    public class LightOn extends Event{
        public LightOn(long delayTime) {
            super(delayTime);
        }
        @Override
        public void action() {light = true;}
        public String toString(){return "Light is on";}

    }
    public class LightOff extends Event{
        public LightOff(long delayTime) {super(delayTime);}
        @Override
        public void action() {light = false;}
        public String toString(){return "Light is off";}
    }
    private boolean water = false;
      public class WaterOn extends Event {
        public WaterOn(long delayTime) { super(delayTime); }
        public void action() {
          // Put hardware control code here.
          water = true;
        }
        public String toString() {
          return "Greenhouse water is on";
        }
    }   
    public class WaterOff extends Event {
      public WaterOff(long delayTime) { super(delayTime); }
      public void action() {
          // Put hardware control code here.
          water = false;
      }
      public String toString() {
        return "Greenhouse water is off";
      }
    }
    private String thermostat = "Day";  
    public class ThermostatNight extends Event {
        public ThermostatNight(long delayTime) {
            super(delayTime);
        }
        public void action() {
          // Put hardware control code here.
          thermostat = "Night";
        }
        public String toString() {
          return "Thermostat on night setting";
        }
     }  
      public class ThermostatDay extends Event {
        public ThermostatDay(long delayTime) {
          super(delayTime);
        }
        public void action() {
          // Put hardware control code here.
          thermostat = "Day";
        }
        public String toString() {
          return "Thermostat on day setting";
        }
      }
    public class Bell extends Event {
        public Bell(long delayTime) { super(delayTime); }
        public void action() {addEvent(new Bell(delayTime));}
        public String toString() { return "Bing!"; }
    }   
    public class Restart extends Event {
        private Event[] eventList;
        public Restart(long delayTime, Event[] eventList) {
          super(delayTime);
          this.eventList = eventList;
          for(Event e : eventList)
            addEvent(e);
        }
        public void action() {
          for(Event e : eventList) {
            e.start(); // Rerun each event
            addEvent(e);
          }
          start(); // Rerun this Event
          addEvent(this);
        }
        public String toString() {
          return "Restarting system";
        }
    }
    public static class Terminate extends Event {
        public Terminate(long delayTime) { super(delayTime); }
        public void action() { System.exit(0); }
        public String toString() { return "Terminating";  }
    }
}

下面通过创建一个GreenhouseControls的一个对象,并添加各种不同的Event对象来配置该系统。

public class GreenhouseControl {
    public static void main(String[] args) {
        GreenhouseControls gc = new GreenhouseControls();
        gc.addEvent(gc.new Bell(900));
        Event[] eventList = {
              gc.new ThermostatNight(0),
              gc.new LightOn(200),
              gc.new LightOff(400),
              gc.new WaterOn(600),
              gc.new WaterOff(800),
              gc.new ThermostatDay(1400)
        };
        gc.addEvent(gc.new Restart(2000, eventList));
        if(args.length == 1)
              gc.addEvent(new GreenhouseControls.Terminate(new Integer(args[0])));
        gc.run();
    }

}
/* Output:
Bing!
Thermostat on night setting
Light is on
Light is off
Greenhouse water is on
Greenhouse water is off
Thermostat on day setting
Restarting system
Terminating

3.内部类的继承

因为内部类的构造器必须链接到其指向外围类的应用,所以在继承内部类是事情会变得复杂。问题在于那个指向外围类的“秘密”应用必须被初始化。而在导出类中不再存在可链接的默认对象。解决这个问题需要用到特殊的语法来说清楚他们之间的关联。

class WithInner{
    class Inner{}
}
public class InheritInner extends WithInner.Inner{
    //InheritInner( ){}不能被编译
    InheritInner(WithInner wi){
        wi.super();
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

可以看到,InheritInner 只继承内部类,不是外围类。但是要生成构造器是默认的构造器不好用。而且不能只传一个外围类对象的应用,必须在构造器中使用encolsingClassReference.super();这样才提供了必要的引用,程序才能编译通过。

4.内部类可以被覆盖吗

覆盖内部类就好像内部类是外围类的一个方法,其实并不起什么作用。

import static net.lijun.util.Print.*;
class Egg{
    private Yolk y;
    protected class Yolk{
        public Yolk(){println("Egg.Yolk()");}
    }
    public Egg(){
        println("new Egg");
        y = new Yolk();
    }
}
public class BigEgg extends Egg{
    public class Yolk{
        public Yolk(){println("BigEgg.Yolk()");}
    }
    public static void main(String[] args) {
        new BigEgg();
    }
}

可以看到当继承某个外围类时内部类并没有发生任何变化,这两个内部类是完全不同的两个实体,各自在自己的命名空间。明确的继承某个内部类是可以的。

import static net.lijun.util.Print.*;
class Egg2 {
      protected class Yolk {
        public Yolk() { println("Egg2.Yolk()"); }
        public void f() { println("Egg2.Yolk.f()");}
      }
      private Yolk y = new Yolk();
      public Egg2() { println("New Egg2()"); }
      public void insertYolk(Yolk yy) { y = yy; }
      public void g() { y.f(); }
}   
public class BigEgg2 extends Egg2{
    public class Yolk extends Egg2.Yolk {
      public Yolk() { print("BigEgg2.Yolk()"); }
      public void f() { print("BigEgg2.Yolk.f()"); }
    }
    public BigEgg2() { insertYolk(new Yolk()); }
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Egg2 e2 = new BigEgg2();
        e2.g();
    }

}

5.局部内部类

前面说过,可以在代码块里创建内部类,典型的创建方式是在一个方法体里创建。局部内部类里面不能有访问说明符,因为不是外围类的一部分;但是可以访问当前代码块里的常量,以及此外围类的所以成员。
下面的例子对局部内部类与匿名内部类的创建进行了比较。

import static net.lijun.util.Print.*;
interface Counter{
    int next();
}
public class LocalInnerClass {
    private int count = 0;
    Counter getCounter(final String name){
        class LocalCounter implements Counter{
            public LocalCounter(){
                println("LocalCounter()");
            }
            public int next() {
                print(name); 
                return count++;             
            }           
        }
        return new LocalCounter();
    }
    Counter getCounter2(final String name) {
        return new Counter() {
          {
            println("Counter()");
          }
          public int next() {
            print(name); 
            return count++;
          }
        };
     }  
    public static void main(String[] args) {
        LocalInnerClass lic = new LocalInnerClass();
        Counter
          c1 = lic.getCounter("Local inner "),
          c2 = lic.getCounter2("Anonymous inner ");
        for(int i = 0; i < 5; i++)
            println(c1.next());
        for(int i = 0; i < 5; i++)
            println(c2.next());
    }
}

Counter返回序列中的下一个值。我们使用了局部内部类和匿名内部类,他们有相同的行为和能力,既然局部内部类的名字在方法外是不可见的,那我们为什么不使用匿名内部类呢?唯一的理由是,我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只适用于实例初始化。
另一个理由是:需要不止一个该内部类对象。

6.内部类标识符

内部类的命名规则是外围类的名字加上’$’再加上内部类的名字。如果内部类是匿名的,编译器会随机产生一个数字作为其标识符,

7.总结

接口和内部类可以解决C++中多继承的问题,此时读者应该了解到他们的语法和语言,在遇到实际情况时我们会最终理解它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值