最近,我的工作很忙,而且10.8小节内容很多、很复杂,即使断断续续的抽时间看了三遍,依然感觉写不好这个小节的笔记。因此,此文中包含10.8小节的部分就当作摘抄,以备后续经常翻看。
10.7 嵌套类
将内部类声明为static,它通常被称为嵌套类。普通的内部类对象隐式地保存了一个指向创建它的外围类对象的引用,而嵌套类则不是这样:
①、要创建嵌套类的对象,不需要外围类的对象。
②、不能从嵌套类的对象中访问非静态的外围类对象。
另外,普通内部类不能包含static方法和static字段(字段可以声明为static final的常量),也不能包含嵌套类,但是嵌套类可以。
练习题18:创建一个包含嵌套类的类。在main()中创建其内部类的实例。
代码如下:
interface MarineAnimals {
void swim();
}
public class Sea {
static class Turtle implements MarineAnimals {
int num = 10;
@Override
public void swim() {
System.out.println("有" + num + "只海龟在游泳。");
}
}
public static MarineAnimals getMarineAnimals() {
return new Turtle();
}
}
运行代码如下:
public static void main(String[] args) {
Sea.Turtle turtle = new Turtle(); // 或MarineAnimals animal = getMarineAnimals()
turtle.swim();
}
练习题19:创建一个包含了内部类的类,而此内部类又包含有内部类。使用嵌套类重复这个过程。注意编译器生成.class文件的名字。
代码如下:
interface MarineAnimals {
void swim();
}
public class Sea {
static class Turtle implements MarineAnimals {
int num = 10;
static class Eye {
}
@Override
public void swim() {
System.out.println("有" + num + "只海龟在游泳。");
}
}
class Anglerfish implements MarineAnimals {
class Eye {
}
@Override
public void swim() {
System.out.println("背上有个小灯笼,在海底爬行");
}
}
public static MarineAnimals getMarineAnimals() {
return new Turtle();
}
}
生成.class文件如下:
10.7.1 接口内部的类
如果想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么就可以使用接口内部的嵌套类。
练习题20:创建一个包含嵌套类的接口,实现此接口并创建嵌套类的实例。
代码如下:
public interface MarineAnimals {
void swim();
static class GoodSwimmer implements MarineAnimals {
@Override
public void swim() {
System.out.println("海洋动物都是游泳健将。");
}
public static void main(String[] args) {
new GoodSwimmer().swim();
}
}
}
练习题21:创建一个包含嵌套类的接口,该嵌套类中有一个static方法,它将调用接口中的方法并显示结果。实现这个接口,并将这个实现的一个实例传递给这个方法。
代码如下:
public interface MarineAnimals {
void swim();
static class GoodSwimmer {
public static void swim(MarineAnimals animals) {
animals.swim();
}
}
}
class Turtle implements MarineAnimals {
@Override
public void swim() {
System.out.println("海龟游速每小时可达32公里");
}
}
class Dolphin implements MarineAnimals {
@Override
public void swim() {
System.out.println("海豚全力前进时,游速每小时可达80公里");
}
}
运行代码如下:
public static void main(String[] args) {
MarineAnimals.GoodSwimmer.swim(new Turtle());
MarineAnimals.GoodSwimmer.swim(new Dolphin());
}
10.8 为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口(诸如ArrayList的内部类Itr、ListItr等)。
问题1:如果只需要一个对接口的引用,为什么不通过外围类实现那个接口呢?
答:如果能满足需求,就应该用外围类实现那个接口。
问题2:用内部类实现一个接口与外围类实现这个接口有什么区别呢?
答:对于一个接口可以有多个不同的内部类实现,而只用外围类,则仅有一个实现(用这个外围类来实现)。因此,用外围类实现不一定能享用接口的方便。
因此,使用内部类最吸引人的原因是:
每个内部类都能独立地继承自一个(接口的)实现,而无论外围类是否已经继承了某个(接口的)实现。
这使得多重继承的解决方案变得完整,接口解决了多重继承的部分问题,内部类有效地实现了多重继承。即,内部类允许继承多个类或抽象类。
书中有两个示例,这里不贴代码了,只是说明一下:
①、在一个类中以某种方式实现两个接口:若问题没有指明应该使用单一类还是内部类,那么仅从实现上来看两种方式没有什么区别。
②、在一个类中以某种方式继承两个类(抽象类或具体类):由于Java是单继承,因此这个类至多只能继承其中一个类,而另一个则必须让内部类来继承。
使用内部类,可以获得的一些特性:
①、内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
②、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
③、创建外围类对象的时候,并不一定创建内部类对象。这使得内部类对象类似于外围类对象的轻量级组件。例如ArrayList与其内部类Itr,我们只有在需要的时候才会去创建Itr对象,然后获得一个ArrayList的视图。(英文原文为:The point of creation of the inner-class objects not tied to the creation of the outer-class object.,这句话我觉得应该翻译为:内部类对象的创建并不会随着外部类对象的创建而创建。)。
④、内部类是一个独立的实体,它与外围类并没有”is-a”(继承)关系。
练习题22:实现Sequence.java中的reverseSelector()方法。
public Selector reverseSelector() {
return new Selector() {
private int i = items.length - 1;
@Override
public void next() {
if (i >= 0)
i--;
}
@Override
public boolean end() {
return i < 0;
}
@Override
public Object current() {
return items[i];
}
};
}
运行代码如下:
public static void main(String[] args) {
Sequence sequence = new Sequence(10);
for (int i = 0; i < 10; i++) {
Employee emp = new Employee(i, "compony" + i);
sequence.add(emp);
}
Selector reverseSelector = sequence.reverseSelector();
while (!reverseSelector.end()) {
System.out.println(reverseSelector.current());
reverseSelector.next();
}
}
练习题23:创建一个接口U,它包含三个方法。创建第一个类A,它包含一个方法,在此方法中通过创建一个匿名内部类,来生成指向U的引用。创建第二个类B,它包含一个由U构成的数组。B应该有几个方法,第一个方法可以接受对U的引用并存储到数组中;第二个方法将数组中的引用设为null(根据方法参数);第三个方法遍历此数组,并在U中调用这些方法(调用U中的方法)。在main()中,创建一组A的对象和一个B的对象。用那些A类对象所产生的U类型的引用填充B对象的数组。使用B回调所有A的对象,再从B中移除某些U的引用。
代码如下:
public interface Fish {
void swim(String desc);
void breathe(String desc);
void dive(String desc);
}
public class SeaFish {
private String name;
public SeaFish(String name) {
this.name = name;
}
public void swim(String desc) {
System.out.println(name + desc);
}
public void breathe(String desc) {
System.out.println(name + desc);
}
public void dive(String desc) {
System.out.println(name + desc);
}
public Fish getFish() {
return new Fish() {
@Override
public void swim(String desc) {
SeaFish.this.swim(desc);
}
@Override
public void breathe(String desc) {
SeaFish.this.breathe(desc);
}
@Override
public void dive(String desc) {
SeaFish.this.dive(desc);
}
};
}
}
public class Sea {
private int i = 0;
private Fish[] fishes;
public Sea(int size) {
fishes = new Fish[size];
}
public void add(SeaFish seaFish) {
if (i < fishes.length) {
fishes[i++] = seaFish.getFish();
}
}
public void remove(int i) {
if (i > 0 && i < fishes.length)
fishes[i - 1] = null;
}
public void show() {
for (Fish fish : fishes) {
if (fish == null)
continue;
fish.swim("在水中快乐嬉戏");
fish.breathe("自由自在的呼吸");
fish.dive("本领很大,会潜水");
}
}
}
运行代码如下:
public class Test {
public static void main(String[] args) {
Sea sea = new Sea(4);
for (int i = 0; i < 4; i++) {
SeaFish fish = new SeaFish("海豚" + i);
sea.add(fish);
}
sea.show();
sea.remove(3);
sea.show();
}
}
10.8.1 闭包与回调
闭包:是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
通过这个定义,可以看出内部类是面向对象的闭包,它不仅包含外围类对象(创建内部类的作用域)的信息,还拥有一个指向此外围类对象的引用,在此作用域内,内部类可以操作所有成员,包括private成员。
回调:在计算机程序设计中,回调函数,或简称回调,是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用高层定义的子程序。
通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。
书中部分代码如下:
interface Incrementable {
void increment();
}
class MyIncrement {
public void increment() {
System.out.println("Other operation");
}
static void f(MyIncrement mi) {
mi.increment();
}
}
class Callee extends MyIncrement {
private int i = 0;
@Override
public void increment() {
super.increment();
System.out.println(++i);
}
private class Closure implements Incrementable {
@Override
public void increment() {
Callee.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) {
callbackReference = cbh;
}
void go() {
callbackReference.increment();
}
}
public class Callback {
public static void main(String[] args) {
Callee callee = new Callee();
Caller caller = new Caller(callee.getCallbackReference());
caller.go();
}
}
说明:
①、Callee继承自MyIncrement,它已经有了一个increment()方法,其与Incrementable的increment()方法无关。因此,不能为了实现Incrementable而覆盖increment()方法,于是只能使用内部类独立地实现Incrementable。
②、内部类Closure实现了Incrementable,以提供一个返回Callee的”钩子”(hook),而且是一个安全(与指针相比)的钩子,它只能调用外围类的increment()方法。
③、Caller的构造器需要一个Incrementable的引用作为参数,然后在以后的某个时刻,Caller对象可以使用此引用回调Callee类。
10.8.2 内部类与控制框架
应用程序框架(application framework):被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决我们的问题(模板方法模式)。模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。
控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作事件驱动系统。比如,Java Swing库就是一个控制系统。
书中示例代码:基于时间触发事件的控制系统。
抽象的事件类:
/**
* 描述了要控制的事件。因为其默认的行为是基于时间去执行控制,所以使用抽象类代替实际的接口。
*/
public abstract class Event {
private long eventTime;
protected final long delayTime;
/**
* 事件创建的时候启动事件,触发事件的时间为System.nanoTime() + delayTime
*
* @param delayTime
*/
public Event(long delayTime) {
this.delayTime = delayTime;
start();
}
/**
* 方便在事件运行以后重新启动计时器,即可以重复使用Event对象
*/
public void start() {
eventTime = System.nanoTime() + delayTime;
}
/**
* 是否就绪,若就绪,则可以运行action()方法(触发事件)
*
* @return
*/
public boolean ready() {
return System.nanoTime() >= eventTime;
}
/**
* 由导出类实现具体的行为
*/
public abstract void action();
}
管理并触发事件的实际控制框架:
/**
* 管理并触发事件的控制器
*/
public class Controller {
// 事件容器
private List<Event> eventList = new ArrayList<Event>();
/**
* 添加事件
*
* @param e
*/
public void addEvent(Event e) {
eventList.add(e);
}
public void run() {
// 直到事件全部被触发,且全部移除
while (eventList.size() > 0) {
/**
* 在进行foreach循环遍历的时候,不能add/remove元素。因此,使用 new
* ArrayList<Event>(eventList)创建一个副本
*/
for (Event e : new ArrayList<Event>(eventList)) {
if (e.ready()) { // 就绪后,触发事件,并移除
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}
}
}
注意,在当前的设计中我们还不知道Event到底要做什么。这正是此设计的关键:使变化的事物与不变的事物相互分离。这里,变化的事物就是各种不同的Event对象所具有的不同行为,而我们可以通过创建不同的Event子类来表现不同的行为。
这正是内部类要做的事情,它允许:
①、控制框架的单个实现是由单个的类创建的,从而使得实现的细节被封装起来。内部类用来表示解决问题所必需的各种不同的action()。
②、内部类能够轻易地访问外围类的任意成员。
此控制框架的一个实现:控制温室的运作,控制灯光、水、温度调节器的开关,以及响铃和重新启动系统。它的每个行为都是不同的,可以使用内部类,在单一的类里产生对同一个基类Event的多种导出版本。
/**
* 温室控制系统
*/
public class GreenhouseControls extends Controller {
// 灯开关的控制
private boolean light = false;
// 开灯
public class LightOn extends Event {
public LightOn(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 控制开灯的硬件代码
light = true;
}
@Override
public String toString() {
return "Light is on";
}
}
// 关灯
public class LightOff extends Event {
public LightOff(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 控制关灯的硬件代码
light = false;
}
@Override
public String toString() {
return "Light is off";
}
}
// 水开关的控制
private boolean water = false;
// 打开水开关
public class WaterOn extends Event {
public WaterOn(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 控制打开水开关的硬件代码
water = true;
}
@Override
public String toString() {
return "Water is on";
}
}
// 关闭水开关
public class WaterOff extends Event {
public WaterOff(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 控制关闭水开关的硬件代码
water = false;
}
@Override
public String toString() {
return "Water is off";
}
}
// 温度调节器,默认为白天模式
private String thermostat = "Day";
// 调整温度调节器为夜间模式
public class ThermostatNight extends Event {
public ThermostatNight(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 调整温度调节器为夜间模式的硬件代码
thermostat = "Night";
}
@Override
public String toString() {
return "Thermostat on night setting";
}
}
// 调整温度调节器为白天模式
public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 调整温度调节器为白天模式的硬件代码
thermostat = "Day";
}
@Override
public String toString() {
return "Thermostat on day setting";
}
}
// 响铃,其行为是将自己的一个实例添加到事件容器中
public class Bell extends Event {
public Bell(long delayTime) {
super(delayTime);
}
@Override
public void action() {
addEvent(new Bell(delayTime));
}
@Override
public String toString() {
return "Bing! Bing! 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);
}
@Override
public void action() {
// 重启每个事件
for (Event e : eventList) {
e.start();
addEvent(e);
}
start(); // 重启当前事件
addEvent(this);
}
@Override
public String toString() {
return "Restarting system";
}
}
// 停止事件
public static class Terminate extends Event {
public Terminate(long delayTime) {
super(delayTime);
}
@Override
public void action() {
System.exit(0);
}
@Override
public String toString() {
return "Terminating";
}
}
}
创建一个GreenhouseControls对象,并添加不同的Event对象来配置该系统。
/**
* 配置并启动温室系统
*/
public class GreenhouseController {
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();
}
}
练习题24:在GreenhouseControls.java中增加一个Event内部类,用以打开、关闭风扇。配置GreenhouseController.java以使用这些新的Event对象。
代码如下:
打开、关闭风扇的Event导出类
// 风扇开关的控制
private boolean fan = false;
// 打开风扇
public class fanOn extends Event {
public fanOn(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 控制打开风扇开关的硬件代码
fan = true;
}
@Override
public String toString() {
return "Fan is on";
}
}
// 关闭风扇
public class fanOff extends Event {
public fanOff(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 控制关闭风扇开关的硬件代码
fan = false;
}
@Override
public String toString() {
return "Fan is off";
}
}
打开、关闭风扇的Event导出类的使用:
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 fanOn(1000), gc.new fanOff(1200), 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();
}
练习题25:在GreenhouseControls.java中继承GreenhouseControls,增加Event内部类,用以开启、关闭喷水机。写一个新版的GreenhouseController.java以使用这些新的Event对象。
代码如下:
GreenhouseControls的导出类,及开启、关闭喷水机的Event导出类:
class HouseControls extends GreenhouseControls {
// 喷水机控制装置
private boolean sprinklerControls = false;
// 打开喷水机
public class TurnOnSprinkler extends Event {
public TurnOnSprinkler(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 硬件代码,打开喷水机开关
sprinklerControls = true;
}
@Override
public String toString() {
return "Sprinkler is on";
}
}
// 打开喷水机
public class TurnOffSprinkler extends Event {
public TurnOffSprinkler(long delayTime) {
super(delayTime);
}
@Override
public void action() {
// 硬件代码,关闭喷水机开关
sprinklerControls = false;
}
@Override
public String toString() {
return "Sprinkler is off";
}
}
}
使用新的Event导出类:
public class HouseController {
public static void main(String[] args) {
HouseControls hc = new HouseControls();
// 可以使用配置文件来避免硬编码
hc.addEvent(hc.new Bell(900));
Event[] eventList = { hc.new ThermostatNight(0), hc.new LightOn(200), hc.new LightOff(400), hc.new WaterOn(600),
hc.new WaterOff(800), hc.new fanOn(1000), hc.new fanOff(1200), hc.new TurnOnSprinkler(1400),
hc.new TurnOffSprinkler(1600), hc.new ThermostatDay(1800) };
hc.addEvent(hc.new Restart(2000, eventList));
// 以命令行输入参数(作为毫秒数)来决定何时终止系统
if (args.length == 1) {
hc.addEvent(new GreenhouseControls.Terminate(new Integer(args[0])));
}
hc.run();
}
}
本文详细探讨了Java内部类的概念,包括嵌套类的特点与应用,以及内部类如何提供进入外围类的窗口。文章通过具体示例介绍了内部类在实现多重继承和解决控制框架问题中的优势。同时,通过温室控制系统案例,展示了内部类在事件驱动系统中的灵活应用。
1455

被折叠的 条评论
为什么被折叠?



