基本概念
内部类 (inner class) 是定义在另一个类中的类。主要特点有:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名 (anonymous) 内部类比较便捷。
使用内部类访问对象状态
public class TalkingClock
{
private int interval:
private boolean beep;
public TalkingClock(int interval, boolean beep) { . . . }
public void start() { . . . }
public class TimePrinter implements ActionListener
// an inner class
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
public void start()
{
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
}
上例中,首先定义了一个 TalkingClock
类,然后在该类内部定义了一个 TimePrinter
类,TimePrinter
就称为 TalkingClock
的一个内部类。
在内部类 TimePrinter
中只定义了一个方法 actionPerformed
,可以发现,在方法内部有一个 beep
变量,它既不是传入参数,也不是方法内部定义的变量,事实上它就是外围类 TalkingClock
的实例域中的 beep
。内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域(包括私有数据域)。
内部类对象总是有一个隐式引用,它指向了创建它的外围类对象。这个引用在内部类的定义中是不可见的,我们将外围类对象的引用称为 outer
,上例中的 beep
等价于 outer.beep
。
外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。因为 TimePrinter
类没有定义构造器,所以编译器为这个类生成了一个默认的构造器:
public TimePrinter(TalkingClock clock) // automatically generated code
{
outer = clock;
}
注意:outer
不是 Java 关键字,而是内部类中的一个特殊机制。
内部类的特殊语法规则
// 使用外围类引用
// OuterClass.this
TimeClock.this
// 内部类构造器
// outerObject.new InnerClass(construction parameters)
ActionListener listener = this.new TimePrinter();
// 此处 this 指向当前外围类对象
// 在外围类作用域外引用内部类
// OuterClass.InnerClass
注意:
- 内部类中声明的所有静态域都必须是
final
。我们希望一个静态域只有一个实例,不过对于每个外部对象,会分别有一个单独的内部类实例。 - 内部类不能有
static
方法。
局部内部类
在上例中,内部类 TimePrinter
只在 start
中被使用,这种情况下,可以考虑使用局部内部类:
public void start()
{
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep():
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
局部类不能用 public
或 private
访问说明符进行声明,它的作用域被限定在声明这个局部类的块中,当 start
方法结束后,这个类也就不存在了。局部类有一个优势,即对外部世界可以完全地隐藏起来。即使 TalkingClock
类中的其他代码也不能访问它。除 start
方法之外,没有任何方法知道 TimePrinter
类的存在。
访问外部变量
与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须事实上为 final
。这说明,它们一旦赋值就绝不会改变。
public void start(int interval, boolean beep)
{
class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
Systea.out.println("At the tone, the tiie is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
上例中,在计时器运行到 if (beep)
时,start
方法已经结束,此时的 beep
参数已不存在,从而产生错误。
为了能够让 actionPerformed
方法工作,TimePrinter
类在 beep
域释放之前将 beep
域用 start
方法的局部变量进行备份。局部类的方法只可以引用定义为 final 的局部变量。鉴于此情况,在列举的示例中,将 beep
参数声明为 final
,对它进行初始化后不能够再进行修改。
public void start(int interval, final boolean beep)
匿名内部类
将局部内部类的使用再深人一步。假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类 (anonymous inner class)。
/*
new SuperType(construction parameters)
{
inner class methods and data
}
*/
public void start(int interval, boolean beep)
{
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone, the time is " + new DateO);
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval, listener);
t.start();
}
这种语法的含义是:创建一个实现 ActionListener
接口的类的新对象,需要实现的方法 actionPerformed
定义在 {}
括号内。
由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类 (super class) 构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。不仅如此,还要像下面这样提供一组括号:
new InterfaceType()
{
methods and data
}
如果构造参数的闭小括号后面跟一个开大括号,正在定义的就是匿名内部类。多年来,Java 程序员习惯的做法是用匿名内部类实现事件监听器和其他回调。如今最好还是使用 lambda
表达式。
// 此程序内部类访问 beep 仍要添加 final 修饰
public void start(int interval, final boolean beep)
{
Timer t = new Timer(interval, event ->
{
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
});
t.start();
}
静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为 static
,以便取消产生的引用。
class ArrayAlg
{
public static class Pair
{
private double first;
private double second;
public Pair(double f, double s)
{
first = f;
second = s;
}
public static Pair(double[] array)
{
double min = Double.POSITIVE_INFINITY;
double max = Double.NECATIVE.INFINITY;
for (double v : array)
{
if (min > v) min = v;
if (max < v) max = v;
}
return new Pair(min, max)
}
public double getFirst() { return first; }
public double getSecond() { return second; }
}
...
}
上例中,在类 ArrayAlg
中定义了一个静态公有内部类 Pair
,在外部可以通过 ArrayAlg.Pair
来访问它。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所冇内部类完全一样。而此例中,必须使用静态内部类,这是由于内部类对象是在静态方法中构造的。
注释:
- 在内部类不需要访问外围类对象的时候,应该使用静态内部类。有些程序员用嵌套类 (nested class) 表示静态内部类。
- 与常规内部类不同,静态内部类可以有静态域和方法。
- 声明在接口中的内部类自动成为
static
和public
类。
参考资料:
- 《Java核心技术 卷1 基础知识》