内部类之二_高级篇

来源:互联网 发布:php 小项目 不用 mvc 编辑:程序博客网 时间:2024/06/08 06:38

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();}//这里只是实现了Incrementableclass 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 settingLight is onLight is offGreenhouse water is onGreenhouse water is offThermostat on day settingRestarting systemTerminating

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++中多继承的问题,此时读者应该了解到他们的语法和语言,在遇到实际情况时我们会最终理解它。

0 0
原创粉丝点击