1.嵌套类
如果不需要内部类对象与其外围类对象之间的联系,那么可以将内部类声明为static。这通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类的对象。然而,当内部类时static时,就不是这样了。
嵌套类意味着:要创建嵌套类的对象,并不需要其外围类的对象;不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通类的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:
public class Parcel8 {
private static class ParcelContents implements Contents{
private int i = 11;
@Override
public int value() {
return i;
}
}
protected static class ParcrlDestination implements Destination{
private String label;
private ParcrlDestination(String whereTo){
label = whereTo;
}
@Override
public String readLabel() {
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(String s) {
return new ParcrlDestination(s);
}
public static Contents contents() {
return new ParcelContents();
}
public static void main(String[] args) {
Contents contents = contents();
Destination destination = destination("test");
}
}
在main()中,没有任何Parcel8的对象时必需的;而是使用选取static成员的普通语法来调用方法–这些方法返回对Contents 和Destination 的引用。嵌套类就没有这个特殊的this的引用,这就使得它类似于一个static方法。其中Destination 和Contents 在上一章博客(第十章接口上)有。
(1)接口内部的类
正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动是public和static的。因为类是static的,只是嵌套类置于接口的命名空间内,这并不违反接口的规则。甚至可以在内部类中实现其外围接口,例子如下:
public interface ClassInInterface{
void howdy();
class Test implements ClassInInterface{
@Override
public void howdy() {
System.out.println(“howdy!”);
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
输出:howdy!
如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么是接口内部的嵌套类会显得很方便。
(2)从多层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要--它能透明地访问所有它所嵌入的外围类所有成员,例子如下:
public class MNA {
private void f(){
System.out.println("f()");
}
class A{
private void g(){
System.out.println("g()");
}
public class B{
void h(){
g();
f();
}
}
}
}
public class MultiNestingAcess {
public static void main(String[] args) {
MNA mna = new MNA();
MNA.A manaa = mna.new A();
MNA.A.B mnaab = manaa.new B();
mnaab.h();
}
}
可以看到MNA.A.B中,调用方法g()和f()不需要任何条件(既是它们定义为private)。这个例子同事展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法。“.new”语法能产生正确的作用域,所以不必在调用构造器时限定类名。
2.为什么需要内部类?
一般说来,内部类继承自某个类的或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的接口。
内部类必须要回答一个问题是:如果只是需要对一个接口的引用,为什么不通过外围类来实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做。”那么内部类实现一个接口与外围类实现这个接口的区别是什么呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类允许继承多个非接口类型(类或抽象类)。
如果拥有的是抽象的类或者具体的类,而不是接口,那就只能使用内部类才能实现多重继承。使用内部类还可以获得其他的特性:
1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
3)创建内部类对象的时刻并不依赖于外围类对象的创建。
4)内部类并没有令人迷惑的“is-a”关系,它就是一个独立的实体。
(1)闭包与回调
闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为他不仅包含外围类的对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。
通过内部类提供闭包的功能是优良的解决方案,例子如下:
public interface Incrementable {
void increment();
}
public class Calleel implements Incrementable{
private int i = 0;
@Override
public void increment() {
i++;
System.out.println(i);
}
}
public class MyIncrement {
public void increment(){
System.out.println("other operation");
}
static void f(MyIncrement mi){
mi.increment();
}
}
public class Calleel2 extends MyIncrement{
private int i = 0;
@Override
public void increment() {
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable{
@Override
public void increment() {
Calleel2.this.increment();
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
public class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh){
callbackReference = cbh;
}
void go(){
callbackReference.increment();
}
}
public class Callbacks {
public static void main(String[] args) {
Calleel calleel1= new Calleel();
Calleel2 calleel2 = new Calleel2();
MyIncrement.f(calleel2);
Caller caller1 = new Caller(calleel1);
Caller caller2 = new Caller(calleel2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
这个例子进一步展示了外围类实现一个接口与内部类实现此接口的区别。就代码而言,Calleel是简单的解决方式。Calleel2继承自MyIncrement,后者已经有了一个不同的increment()方法,并且与Incrementable接口期望的increment()方法完全不相关。所以Calleel2继承了MyIncrement,就不能为了Incrementable的用途而覆盖increment()方法,于是只能使用内部类独立地实现Incrementable。还要注意,当创建一个内部类时,并没有在外围类的接口中添加东西,也咩有修改外围类的接口。
注意,在Calleel2中除了getCallbackReference()以外,其他成员都是private的。要想建立与外部世界的任何连接,interface Incrementable。都是必需的。在这里可以看到,interface是如何允许接口与接口的实现完全独立。
(2)内部类与控制框架
应用程序框架就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。
首先,接口描述了要控制的事件。因为其默认的行为是基于时间去执行控制,所以使用抽象类代替实际的接口。
注意,内部类允许:
1)控制框架的完整实现时由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的action。
2)内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码变得令人讨厌,以至于你肯定会选择其他的方法。
考虑此控制框架的一个特定实现,如控制温室的运作:控制灯光、水、温度调节器的开关,以及响铃和重新启动系统,每个行为都是完全不同的。控制框架的设计使得分离这些不同的代码变得非常容易。使用内部类,可以在单一的类里面产生对同一个基类的多种到处版本。对于温室系统的每种行为,都继承一个新的Event内部类,并在要实现的action()中编写控制代码。
作为典型的应用程序框架,GreenhouseControls 继承Controller:
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();
}
public class Controller {
private List<Event> eventList = new ArrayList<Event>();
public void addEvent(Event c){
eventList.add(c);
}
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);
}
}
}
}
}
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;
}
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 WaaterOn extends Event{
public WaaterOn(long delayTime) {
super(delayTime);
}
@Override
public void action() {
water = true;
}
public String toString(){
return "Greenhouse water is on";
}
}
public class WaaterOff extends Event{
public WaaterOff(long delayTime) {
super(delayTime);
}
@Override
public void action() {
water = false;
}
public String toString(){
return "Greenhouse water is off";
}
}
private String thermostatString = "Day";
public class ThermostatNight extends Event{
public ThermostatNight(long delayTime) {
super(delayTime);
}
@Override
public void action() {
thermostatString = "Night";
}
public String toString(){
return "Thermostat on night setting";
}
}
public class ThermostatDay extends Event{
public ThermostatDay(long delayTime) {
super(delayTime);
}
@Override
public void action() {
thermostatString = "Day";
}
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));
}
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 event : eventList) {
addEvent(event);
}
}
@Override
public void action() {
for (Event e : eventList) {
e.start();
addEvent(e);
}
start();
addEvent(this);
}
public String toString(){
return "Restaring system";
}
}
public static class Terminate extends Event{
public Terminate(long delayTime) {
super(delayTime);
}
@Override
public void action() {
System.exit(0);
}
public String toString(){
return "Terminating";
}
}
}
注意light、water和thermostatString都属于外围类GreenhouseControls,而这些内部类能够自有地访问那些字段,无需限定条件或特殊许可。而且action()方法通常都设计对某种硬件的控制。大多数Event类看起来都很相似,但是Bell和Restart则比较特别。Bell控制响铃,然后再事件列表中增加一个Bell对象,于是过一会它可以再次响铃。内部类时多么像多重继承:Bell和Res方法。
一个有Event对象组成的数组被递交给Restart,该数组要加到控制器上。由于Restart也是一个Event对象,所以同样可以将Restart对象添加到Restart.action()中,以使系统能够有规律地重新启动自己。
下面的类通过创建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 WaaterOn(600),
gc.new WaaterOff(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();
}
}
这个类的作用是初始化系统,所以它添加了所有相应的事件。Restart事件反复运行,而且它每次都会将eventList加载到GreenhouseControls对象中。如果提供了命令行参数,系统会以它作为毫秒数,决定什么时候终止程序。