Inner Classes 在平常的应用中虽然不是很多,但是仍然有很多细节问题值得注意,需要掌握。因此,这里对这方面内容做一小结。
Inner Classes,顾名思义,就是定义在另一个类中的类,之所以出现这一概念,主要是因为它具有如下两个特点:
- inner classes中的方法可以访问类定义域内的所有数据, 哪怕数据是private的。
- inner classes对同包内的其它类是不可见的。
常规内部类
下面,我将针对一段具体代码,对Inner Classes的特性和应用作具体的阐述。
public class TimerTestForInnerClass {
public static void main(String[] args) {
TalkingClock clock = new TalkingClock(100, true);
clock.start();
JOptionPane.showMessageDialog(null, "Quit Program?");
System.exit(0);
}
}
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();
}
public class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
Date now = new Date();
System.out.println("At the tone, the time is " + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
}
在上面的代码中,TimePrinter是TalkingClock的内部类。仔细观察,发现actionPerformed方法中引用的beep变量在TimePrinter中并没有定义,它是外部类TalkingClock的成员变量。这就是inner classes特点一的展示,之所以可以如此,是因为inner class在被创建时获得了外部类的一个引用,这个引用是由编译器自动传给内部类的构造函数的,因此,内部类中使用的beep变量的完整形式其实是:
if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
上述代码中的OuterClass.this代表的就是外部类的引用。
如果内部类是public的,那么我们也可以在外部类以外来创建内部类,方法如下:
TalkingClock jabberer = new TalkingClock(1000, true);
TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
Local Inner Classes 局部内部类
内部类除了可以作为外部类的成员,还可以定义在外部类的方法中,这样的内部类称为:Local Inner Classes。Local Inner Classes不需要访问权限修饰符(access specifier),因为它们的作用域受它们被定义的块域的限制。Local Inner Classes的一个重要的优点是,除了定义它们的方法以外,外界任何类或方法都无法访问它们。因此,当一个内部类只需要被一个方法使用,并且想屏蔽任何其他方法时,就可以将它们定义为Local的。
public void start() {
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
Date now = new Date();
System.out.println("At the tone, the time is " + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
上面的代码就是Local Inner Class的应用实例。
Local Inner Class不光可以使用Outer Class的成员变量,还可使用它所在方法的参数以及方法中的局部变量。但要注意一点,这些外部引用变量都必须是final型,否则会收到编译器错误。见下面的代码:
public void start(int interval, final boolean beep) {
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
Date now = new Date();
System.out.println("At the tone, the time is " + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
我们来简单了解下beep参数是如何传入内部类的方法中。直观来看,beep变量是无法存留到actionPerformed方法执行之时的,它在start()方法执行结束之后就被回收了,而actionPerformed则要过短时间才执行,因此,为了正常使用beep变量,我们需要为它做一个copy,并将这个copy传进内部类中。所以,内部类中所谓的beep,其实是原有变量的副本。
TIP:为什么只有final型的参数或局部变量可以传入inner class?(整理自Core Java,如有不妥,敬请指正)
在很多时候,这个final的限制其实为我们带来了很多不便,因为我们有的时候确实是想要在inner class中改变局部变量的,如下面这段代码:
int counter = 0;
Date[] dates = new Date[100];
for (int i = 0; i < dates.length; i++)
dates[i] = new Date() {
public int compareTo(Date other) {
counter++; // ERROR
return super.compareTo(other);
}
};
Arrays.sort(dates);
System.out.println(counter + " comparisons.");
我们只是想获取比较次数,但是由于内部类的final限制,这样的代码显然是不合法的。但如果直接加上final,我们的update目标又无法达到,所以,只能做如下的转换:
final int[] counter = new int[1];
for (int i = 0; i < dates.length; i++)
dates[i] = new Date() {
public int compareTo(Date other) {
counter[0]++;
return super.compareTo(other);
}
};
这样,虽然counter是final的,但是我们却可以随意改变它内部的值,从而变相的达到了update的目标。
事实上,在inner class起初创建时,这种从primitive型到array型的转换最开始是由编译器自动完成的,但是开发者们很害怕编译器在“背着他们”创建那么些对象,因此,最后还是决定加上final这个限制,如果需要在inner class中改变变量值,那就让programer自己去做转换。而且,也并不排除,随着Java语言的不断完善,会有更好的方式来替代final限制,达到原有的安全效果。