一、前言
可将一个类定义置入另一个类定义中,这就叫作“内部类”。内部类对我们非常有用,因为利用它可对那些逻辑上相互联系的类进行分组,并可控制一个类在另一个类里的“可见性”
二、内部类的定义:
public class Parcel1 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } } // Using inner classes looks just like // using any other class, within Parcel1: public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); } public static void main(String[] args) { Parcel1 p = new Parcel1(); p.ship("Tanzania");
Parcel1.Contents c = p.new Contents() ; } } // /:~
注意创建内部类对象的方式
三、在方法或者作用域中使用内部类
3.1在一个方法或者任意的作用域中使用内部类一般有两个目的:
1:准备实现某种形式的接口,使自己的程序能创建或者返回一个句柄
2: 要解决一个复杂的问题,并希望创建一个类用来辅助自己的程序方案 同时又不愿意把它公开
为目的1来一个例子:
//: Parcel3.java // Returning a handle to an inner class package c07.parcel3; abstract class Contents { abstract public int value(); } interface Destination { String readLabel(); } public class Parcel3 { private class PContents extends Contents { private int i = 11; public int value() { return i; } } protected class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } public Destination dest(String s) { return new PDestination(s); } public Contents cont() { return new PContents(); } } class Test { public static void main(String[] args) { Parcel3 p = new Parcel3(); Contents c = p.cont(); Destination d = p.dest("Tanzania"); // Illegal -- can't access private class: // ! Parcel3.PContents c = p.new PContents(); } } // /:~
3.2 在一个方法内创建内部类
为例子的需要先创建两个接口:
//: Destination.java package c07.innerscopes; interface Destination { String readLabel(); } ///:~ //: Contents.java package c07.innerscopes; interface Contents { int value(); } ///:~ //: Wrapping.java package c07.innerscopes; public class Wrapping { private int i; public Wrapping(int x) { i = x; } public int value() { return i; } } ///:~
在一个方法中定义一个内部类 :
//: Parcel4.java // Nesting a class within a method package c07.innerscopes; public class Parcel4 { public Destination dest(String s) { //在方法的一个作用域内定义的类 class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; //TODO handle label } public String readLabel() { return label; } } return new PDestination(s); } public static void main(String[] args) { Parcel4 p = new Parcel4(); Destination d = p.dest("Tanzania"); System.out.println(d.readLabel()); ; } } // /:~
方法dest需要返回一个Destination 类型的对象,通常的操作是在方法外部(在同一个类中定义一个内部类或者新增加一个类)实现一个Destination 接口的类 然后在dest方法中返回该类对象
这样子做的话很显然会将该类的实现暴漏在dest方法外部 如果操作只对dest方法可见(或者说是唯一针对该方法的操作) 那么在类中定义一个内部类很有必要。当然,不能由于类PDestination 的名字置于
dest()内部,就认为在dest()返回之后PDestination 不是一个有效的对象。
3.3在任意的作用域内定义内部类
//: Parcel5.java // Nesting a class within a scope package c07.innerscopes; public class Parcel5 { private void internalTracking(boolean b) { if (b) { class TrackingSlip { private String id; TrackingSlip(String s) { id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip("slip"); String s = ts.getSlip(); System.out.println(s); } // Can't use it here! Out of scope: // ! TrackingSlip ts = new TrackingSlip("x"); } public void track() { internalTracking(true); } public static void main(String[] args) { Parcel5 p = new Parcel5(); p.track(); } } // /:~
TrackingSlip 类嵌套于一个if 语句的作用域内。这并不意味着类是有条件创建的——它会随同其他所有东西得到编译。然而,在定义它的那个作用域之外,它是不可使用的。除这些以外,它看起来和
一个普通类并没有什么区别。
3.4 匿名内部类
//: Parcel6.java // A method that returns an anonymous inner class package c07.innerscopes; public class Parcel6 { public Contents cont() { return new Contents() { private int i = 11; public int value() { return i; } }; // Semicolon required in this case } public static void main(String[] args) { Parcel6 p = new Parcel6(); Contents c = p.cont(); System.out.println(c.value()); } } // /:~
在匿名内部类中,Contents 是用一个默认构建器创建的,那么如果需要基础类中构造方法含有参数又如何是实现呢?
//: Parcel7.java // An anonymous inner class that calls the // base-class constructor package c07.innerscopes; public class Parcel7 { public Wrapping wrap(int x) { // Base constructor call: return new Wrapping(x) { public int value() { return super.value() * 47; } }; // Semicolon required } public static void main(String[] args) { Parcel7 p = new Parcel7(); Wrapping w = p.wrap(10); } } // /:~
要注意的是:匿名内部类既然是匿名的 那么肯定是没有构造方法的(匿名内部类没有构造方法) 那么一个新的问题又来了,没有构造方法 如何初始化呢?
//: Parcel8.java // An anonymous inner class that performs // initialization. A briefer version // of Parcel5.java. package c07.innerscopes; public class Parcel8 { // Argument must be final to use inside // anonymous inner class: public Destination dest(final String dest) { return new Destination() { private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel8 p = new Parcel8(); Destination d = p.dest("Tanzania"); } } // /:~
通过上面的例子知道: 可在定义自己的字段时进行初始化,但想使用在匿名内部类外部定义的一个对象,则编译器要求外部对象为final属性。不过新的问题又来了 按照上面的初始化只能对简单
类的属性进行初始化 ,如果要想进行一些逻辑操作的话 上面那种方法显然不适用,此时又如何处理? 联系到第一节讲的初始化顺序 可以通过一个普通代码块来模拟一个构造代码块
//: Parcel9.java // Using "instance initialization" to perform // construction on an anonymous inner class package c07.innerscopes; public class Parcel9 { public Destination dest(final String dest, final float price) { return new Destination() { private int cost; // Instance initialization for each object: { //普通代码块来模拟一个构造代码块 System.out.println("Instance initialization for each object:"); cost = Math.round(price); if (cost > 100) //逻辑判断 System.out.println("Over budget!"); } private String label = dest; public String readLabel() { System.out.println(label); return label; } }; } public static void main(String[] args) { Parcel9 p = new Parcel9(); Destination d = p.dest("Tanzania", 101.395F); } } // /:~
四、 访问外部类
前面的例子都展示的是通过内部类来影藏或者代码组织的方法 ,但似乎并不特别引人注目。然而,我们还忽略了另一个重要的事实。创建自己的内部类时,那个类的对象同时拥有指向封装对象(这些对象封装或生成了内部类)的一个链接。所以它们能访问那个封装对象的成员——毋需取得任何资格。除此以外,内部类拥有对封装类所有元素的访问权限。
//: Sequence.java // Holds a sequence of Objects interface Selector { boolean end(); Object current(); void next(); } public class Sequence { private Object[] o; private int next = 0; public Sequence(int size) { o = new Object[size]; } public void add(Object x) { if (next < o.length) { o[next] = x; next++; } } private class SSelector implements Selector { int i = 0; public boolean end() { return i == o.length; } public Object current() { return o[i]; } public void next() { if (i < o.length) i++; } } public Selector getSelector() { return new SSelector(); } public static void main(String[] args) { Sequence s = new Sequence(10); for (int i = 0; i < 10; i++) s.add(Integer.toString(i)); Selector sl = s.getSelector(); while (!sl.end()) { System.out.println((String) sl.current()); sl.next(); } } } // /:~
五、内部类的继承问题
由于内部类构建器必须同封装类对象的一个句柄联系到一起,所以从一个内部类继承的时候,情况会稍微变得有些复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化,而且在衍生类中不再有一个默认的对象可以连接。解决这个问题的办法是采用一种特殊的语法,明确建立这种关系
//: InheritInner.java // Inheriting an inner class class WithInner { class Inner {} } public class InheritInner extends WithInner.Inner { //! InheritInner() {} // Won't compile InheritInner(WithInner wi) { wi.super(); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } } ///:~
如果继承一个父类,父类中定义了一个内部类,那么在子类中能覆盖内部类吗?
//: BigEgg.java // An inner class cannot be overriden // like a method class Egg { protected class Yolk { public Yolk() { System.out.println("Egg.Yolk()"); } } private Yolk y; public Egg() { System.out.println("New Egg()"); y = new Yolk(); } } public class BigEgg extends Egg { public class Yolk { public Yolk() { System.out.println("BigEgg.Yolk()"); } } public static void main(String[] args) { new BigEgg(); } } // /:~
默认构建器是由编译器自动合成的,而且会调用基础类的默认构建器。大家或许会认为由于准备创建一个BigEgg,所以会使用Yolk 的“被覆盖”版本。但实际情况并非如此。输出如下:
New Egg()
Egg.Yolk()
这个例子简单地揭示出当我们从外部类继承的时候,没有任何额外的内部类继续下去。然而,仍然有可能“明确”地从内部类继承:
//: BigEgg2.java // Proper inheritance of an inner class class Egg2 { protected class Yolk { public Yolk() { System.out.println("Egg2.Yolk()"); } public void f() { System.out.println("Egg2.Yolk.f()"); } } private Yolk y = new Yolk(); public Egg2() { System.out.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() { System.out.println("BigEgg2.Yolk()"); } public void f() { System.out.println("BigEgg2.Yolk.f()"); } } public BigEgg2() { insertYolk(new Yolk()); } public static void main(String[] args) { Egg2 e2 = new BigEgg2(); e2.g(); } } // /:~
new BigEgg2() 执行分析: * BigEgg2 extends Egg2 因此需要先执行Egg2中的变量初始化 + 构造方法: * 初始化y 打印Egg2.Yolk() 构造方法打印:New Egg2() * -->接着执行 BigEgg2的构造方法 调用 new Yolk() 此时的new Yolk 调用的是BigEgg2中的Yolk类的构造方法(覆盖了父类的方法) * 是调用该构造方法的时候要先调用父类的构造方法 打印Egg2.Yolk() 然后调用本身Yolk构造方法打印 BigEgg2.Yolk() * 调用 e2.g() 打印的是BigEgg2中的 Yolk的f() 打印 BigEgg2.Yolk.f() * 因此打印结果如下: * Egg2.Yolk() * New Egg2() * Egg2.Yolk() * BigEgg2.Yolk() * BigEgg2.Yolk.f()
六、控制框架
一个“应用程序框架”是指一个或一系列类,它们专门设计用来解决特定类型的问题。为应用应用程序框架,我们可从一个或多个类继承,并覆盖其中的部分方法。我们在覆盖方法中编写的代码用于定制由那些
应用程序框架提供的常规方案,以便解决自己的实际问题。“控制框架”属于应用程序框架的一种特殊类型受到对事件响应的需要的支配;主要用来响应事件的一个系统叫作“由事件驱动的系统”。在应用程序设
计语言中,最重要的问题之一便是“图形用户界面”(GUI),它几乎完全是由事件驱动的。 AWT 属于一种控制框架,它通过内部类完美地解决了GUI 的问题。为理解内部类如何简化控制框架的创建与使用,
可认为一个控制框架的工作就是在事件“就绪”以后执行它们。 请认识到针对控制框架需要控制的东西,框架内并未包含任何特定的信息。首先,它是一个特殊的接口,描述了所有控制事件。它可以是一个抽
象类,而非一个实际的接口。
//: Event.java
// The common methods for any control event
package c07.controller;
abstract public class Event {
private long evtTime;
public Event(long eventTime) {
evtTime = eventTime;
}
public boolean ready() {
return System.currentTimeMillis() >= evtTime;
}
abstract public void action();
abstract public String description();
} // /:~
//: Controller.java // Along with Event, the generic // framework for all control systems: package c07.controller; // This is just a way to hold Event objects. class EventSet { private Event[] events = new Event[100]; private int index = 0; private int next = 0; public void add(Event e) { if (index >= events.length) return; // (In real life, throw exception) events[index++] = e; } public Event getNext() { boolean looped = false; int start = next; do { next = (next + 1) % events.length; //可以循环遍历下去 // See if it has looped to the beginning: if (start == next) looped = true; // If it loops past start, the list // is empty: if ((next == (start + 1) % events.length) && looped) return null; } while (events[next] == null); return events[next]; } public void removeCurrent() { events[next] = null; } } public class Controller { private EventSet es = new EventSet(); public void addEvent(Event c) { es.add(c); } public void run() { Event e; while ((e = es.getNext()) != null) { if (e.ready()) { e.action(); System.out.println(e.description()); es.removeCurrent(); } } } } // /:~
设计了Event类我们仍然不能准确地知道一个“事件”要做什么。这正是整个设计的关键,它怎样“将发生变化的东西同没有变化的东西区分开”?或者用我的话来讲,“改变的意图”造成了各类Event 对象
的不同行动。我们通过创建不同的Event 子类,从而表达出不同的行动。这里正是内部类大显身手的地方。
它们允许我们做两件事情:
(1) 在单独一个类里表达一个控制框架应用的全部实施细节,从而完整地封装与那个实施有关的所有东西。内部类用于表达多种不同类型的action(),它们用于解决实际的问题。除此以外,后续的例子
使用了private 内部类,所以实施细节会完全隐藏起来,可以安全地修改。
(2) 内部类使我们具体的实施变得更加巧妙,因为能方便地访问外部类的任何成员。若不具备这种能力,代码看起来就可能没那么使人舒服,最后不得不寻找其他方法解决。
具体实施方式,它设计用来控制温室(Greenhouse)功能 每个行动都是完全不同的:控制灯光、供水以及温度自动调节的开与关,控制响铃,以及重新启动系统。但控
制框架的设计宗旨是将不同的代码方便地隔离开。对每种类型的行动,都要继承一个新的Event 内部类,并在action() 内编写相应的控制代码。
//: GreenhouseControls.java // This produces a specific application of the // control system, all in a single class. Inner // classes allow you to encapsulate different // functionality for each type of event. package c07.controller; public class GreenhouseControls extends Controller { private boolean light = false; private boolean water = false; private String thermostat = "Day"; private class LightOn extends Event { public LightOn(long eventTime) { super(eventTime); } public void action() { // Put hardware control code here to // physically turn on the light. light = true; } public String description() { return "Light is on"; } } private class LightOff extends Event { public LightOff(long eventTime) { super(eventTime); } public void action() { // Put hardware control code here to // physically turn off the light. light = false; } public String description() { return "Light is off"; } } private class WaterOn extends Event { public WaterOn(long eventTime) { super(eventTime); } public void action() { // Put hardware control code here water = true; } public String description() { return "Greenhouse water is on"; } } private class WaterOff extends Event { public WaterOff(long eventTime) { super(eventTime); } public void action() { // Put hardware control code here water = false; } public String description() { return "Greenhouse water is off"; } } private class ThermostatNight extends Event { public ThermostatNight(long eventTime) { super(eventTime); } public void action() { // Put hardware control code here thermostat = "Night"; } public String description() { return "Thermostat on night setting"; } } private class ThermostatDay extends Event { public ThermostatDay(long eventTime) { super(eventTime); } public void action() { // Put hardware control code here thermostat = "Day"; } public String description() { return "Thermostat on day setting"; } } // An example of an action() that inserts a // new one of itself into the event list: private int rings; private class Bell extends Event { public Bell(long eventTime) { super(eventTime); } public void action() { // Ring bell every 2 seconds, rings times: System.out.println("Bing!"); if (--rings > 0) addEvent(new Bell( System.currentTimeMillis() + 2000)); } public String description() { return "Ring bell"; } } private class Restart extends Event { public Restart(long eventTime) { super(eventTime); } public void action() { long tm = System.currentTimeMillis(); // Instead of hard-wiring, you could parse // configuration information from a text // file here: rings = 5; addEvent(new ThermostatNight(tm)); addEvent(new LightOn(tm + 1000)); addEvent(new LightOff(tm + 2000)); addEvent(new WaterOn(tm + 3000)); addEvent(new WaterOff(tm + 8000)); addEvent(new Bell(tm + 9000)); addEvent(new ThermostatDay(tm + 10000)); // Can even add a Restart object! addEvent(new Restart(tm + 20000)); } public String description() { return "Restarting system"; } } public static void main(String[] args) { GreenhouseControls gc = new GreenhouseControls(); long tm = System.currentTimeMillis(); gc.addEvent(gc.new Restart(tm)); gc.run(); } } // /:~