使用内部类的原因:
(1)可以访问该类定义所在作用域中的数据,包括私有数据。
(2)可以对同一个包中的其它类隐藏起来。
(3)当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
下面主要分几种情况进行讨论:
(a)使用内部类访问对象状态
class TalkingClock { private int interval; private boolean beep; public TalkingClock(int interval, boolean beep) { this.interval = interval; this.beep = beep; } public void start() { ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); } /** 内部类 **/ /**将TimePrinter声明为私有,只有TalkingClock能够生成**/ private class TimePrinter implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (beep) { Toolkit.getDefaultToolkit().beep(); } } } }
为了能够让程序正常运行,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。
这个引用在内部类的定义中是不可见的。为了进一步说明,将外围类对象的引用称为outer。actionPerformed方法等价于下列形式:
public void actionPerformed(ActionEvent e) { if (outer.beep) { Toolkit.getDefaultToolkit().beep(); } }
外围类的引用在构造器中设置。,编译器修改了所有内部类的构造器,并添加一个外围类引用的参数。
由于TimePrinter没有构造器,编译器会为其生成一个默认构造器(outer在此仅用来说明内部类的机制):
public TimePrinter(TalkingClock clock){ outer = clock }
当在start方法中创建了TimePrinter对象后,编译器会将this引用传递给当前的构造器:
ActionListener listener = new TimePrinter(this);
(b)局部内部类
在上面的代码中,TimePrinter类只在start方法中创建这个类型的对象时使用了一次。
当遇到类似情形时,可以在方法中定义局部类:
public void start() { class TimePrinter implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (beep) { Toolkit.getDefaultToolkit().beep(); } } } }
(c)由外部方法访问final变量
局部类内部类不仅能访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须被声明为final。
将TalkingClock构造器中的参数interval和beep移到start方法中。
public void start(int interval, final boolean beep) { class TimePrinter implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (beep) { Toolkit.getDefaultToolkit().beep(); } } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); }
需要注意的是:TalkingClock类不再需要存储实例变量beep了,它只是引用start方法中的beep参数变量。
程序if(beep)... 在start方法内部,为什么不能访问beep变量的值呢?
内部的控制流程如下:
(1)调用start方法。
(2)调用内部的TimePrinter构造器,以初始化对象变量listener。
(3)将listener引用传递给Timer构造器,定时器打开,start方法结束。
此时start方法的beep参数变量不复存在。
(4)然后,actionPerformed方法执行if(beep)....。
为了能够让actionPerformed方法能够正常工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行了备份。
编译器为局部内部类构造了名为TalkingClock$TimePrinter。
class TalkingClock$1TimePrinter{ TalkingClock$1TimePrinter(TalkingClock, boolean); public void actionPerformed(java.awt.event.ActionEvent); final boolean val$beep; final TalkingClock this$0; }
注意构造器的boolean参数和val$beep实例变量。当创建一个对象时,beep就会被传递给构造器,并存储在val$beep域中。
编译器必须检测对局部变量的访问,为每一个变量建立相应的数据,并将局部变量拷贝到构造器中。