内部类(inner class)是定义在另一个类中的类。为什么要使用内部类呢?其主要原因有一下三点:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷
(1)使用内部类访问对象状态
下面将进一步分析TimerTest示例,并抽象出一个TalkiingClock类。构造一个语音时钟需要提供两个参数:发布通告的间隔和开关铃声的标志。
public class TalkingClock {
private int interval;
private boolean beep;
public talkingClock(int interval, boolean beep){...}
public class TimePrinter implements ActionListener {
// an inner class
...
}
}
需要注意,这里的TimePrinter类位于TalkingClock类内部。这并不意味着每个TalkingClock都有一个TimePrinter实例域。如前面所示,TimePrinter对象是由TalkingClock类的方法构造。
public class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event) {
if(beep) Toolkit.getDefaultToolkit().beep();
}
}
TimePrinter类没有实例域或者名为beep的变量,取而代之的是beep引用了创建TimePrinter的TalkingClock对象的域。一个方法可以调用这个方法的对象数据域。内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
这个引用在内部类的定义中是不可见的。然而,为了说明这个概念,我们将外围类对象的引用称为outer。于是actionPerformed方法将等价于下列形式:
public void actionPerformed(ActionEvent event) {
System.out.println("At the tone, the time is " + new Date());
if(outer.beep) Toolkit.getDefaultToolkit().beep();
}
外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。因为TimePrinter类没有定义构造函数,所以编译器为这个类生成了一个默认构造器:
public TimePrinter(TalkingClock clock)
{
outer = clock;
}
TimePrinter类声明为私有的。这样一来,只有TalkingClock的方法才能够构造TimePrinter对象。只有内部类可以是私有类,而常规类只可以具有包可见性,或共有可见性。
(2)内部类的特殊语法规则
已经讲述了内部类有一个外围类的引用outer。事实上使用外围类引用的正规语法还要复杂一些。表达式OuterClass.this
表示外围类引用。例如:
public void actionPerformed(ActionEvent event) {
if(TalkingClock.this.beep)...
}
//反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:
//outerObject.new InnerClass(construction parameters)
ActionListener listener = this.new TimePrinter();
(3)内部类是否有用、必须和安全
内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成分美元符号隔外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。
例如,在TalkingClock类内部的TimePrinter类将被翻译成类文件TalkingClock$TimePrinter.class.
内部类可以访问外围类的私有(private)数据
(4)局部内部类
public void start() {
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
...
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
局部类不能用public
或者private
访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。
局部类有一个优势,即对外部世界可以完全隐藏起来。即使TalkingClock类中的其他代码也不能访问它。除了start方法之外,没有任何方法知道TimePrinter类的存在。
(5)由外部方法访问变量
与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须事实上为final,这说明,它们一旦赋值就决不会改变。
public void start(int interval, boolean beep) {
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
if(beep){...};
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
(6)匿名内部类
假设只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类。
public void start(int interval, boolean beep)
{
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent event)
{
if(beep){...}
}
};
Timer t = new Timer(interval, listener);
t.start();
}
(7)静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围对象。因此,可以将内部类声明为static,以便取消生产的引用。
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for(double v : values) {
if(min > v) min = v;
if(max < v) max = v;
}
//然而,这个方法必须返回两个数值,为此,可以定义一个包含两个值的类Pair:
class Pair {
private double first;
private double second;
public Pair(double f, double s) {
this.first = f;
this.second = s;
}
public double getFirst() {return this.first;}
public double getSecond() {return this.second;}
}
//min max方法可以返回一个Pair类型的对象
class ArrayAlg {
public static Pair minmax(double[] values) {
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for(double v : values) {
if(min > v) min = v;
if(max < v) max = v;
}
return new Pair(min, max)
}
}
//调用
Pair p = ArrayAlg.mnmax(d);
不过,与前面例子中所使用的内部类不同,在Pair对象中不需要引用任何其他的对象,为此,可以将这个内部类声明为static:
class ArrayAlg {
public static class Pair{
...
}
...
}
当然,只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。