一、内部类概述
内部类是定义在另一个类中的类。使用内部类的原因有以下三点:
- 内部类方法可以访问该类定义所在的作用于中的数据,包括私有的数据
- 内部类可以对同一个包中的其他类隐藏起来
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷
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.getDefaultToopkit().beep(); // beep是外部类的private域,但是可以在内部类中直接访问
}
}
}
从以上代码段中可以看出,内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
内部类的对象总有一个隐式引用,它指向了创建它的外部类对象,这个引用在内部类的定义中是不可见的,可以把它称为outer(注意outer不是Java的关键字,只是为了说明内部类的机制而引入这一称呼),则以上代码段等价为:
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 (outer.beep) Toolkit.getDefaultToolkit().beep(); // outer是对外围类的引用,实际上Java中并不存在这个关键字
}
}
}
实际上,在TalkingClock的某方法中创建TimePrinter类的对象时,编译器会把this引用自动传递给构造器:
ActionListener listener = new TimePrinter(this); // this由编译器自动传入
而由于TimePrinter没有定义构造器,所以编译器为这个类生成了一个默认的构造器:
public TimePrinter(TalkingClock clock) { // 由编译器自动生成
outer = clock;
}
这就是内部类中获得外部类的隐式引用的机制。
实际上在内部类中要获得外部类的引用,可以使用这样的语法:
OuterClass.this
依然选择之前的例子,可以像这样编写actionPerformed方法:
public void actionPerformed(ActionEvent event) {
System.out.println("At the tone, the time is " + new Date());
if (TalkingClock.this.beep) Toolkit.getDefaultToopkit().beep(); // outer是对外围类的引用,实际上Java中并不存在这个关键字
}
此处把TimePrinter设置为public,则所有其他的类和方法都可以看到这个内部类。若要在其他类中构造TimePrinter类的对象,需要使用这样的语法:
TalkingClock jabberer = new TalkingClock(1000, true);
TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
而如果把TimePrinter设置为private,则只有TalkingClock类的方法可以看到TimePrinter并构造这个类的对象。只有内部类可以设置为private,而常规类只可以具有包可见性(默认情况)或公有可见性(public)。
注意:
- 内部类中声明的所有static域都必须是final:我们希望一个静态域只有一个实例,不过对于每个外部对象,会分别有一个单独的内部类实例。如果这个域不是final,它就有可能不唯一
- 内部类不能有static方法
二、局部内部类
如果某个内部类只在外部类的某一个方法中被使用到,则可以把内部类定义在这个方法中,称为局部类:
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进行声明,它的作用域被限定在声明这个局部类的块中。
局部类可以对外部世界完全地隐藏起来,即使TalkingClock类中的其他代码也不能访问它。除start方法之外,没有任何方法知道TimePrinter类的存在。
与其他内部类相比,局部类不仅能够访问包含它们的外部类,还可以访问局部变量。不过那些局部变量必须事实上为final(effectively final):
// 把interval和beep作为参数传入start函数中,此时interval和beep成为局部变量,由于在局部内部类中使用到了beep,因此beep必须事实上为final,无法在方法actionPerformed中修改beep的值
public void start(int interval, boolean beep) {
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();
}
若确实需要在局部类中修改局部变量的值,则可以通过传递一个长度为1的数组来解决。
三、匿名内部类
假如只创建这个类的一个对象,就不必命名这个类了。这种类被称为匿名内部类:
public void start(int interval, final boolean beep) {
ActionListener listener = new ActionLIstener() { // 匿名内部类实现了ActionListener接口
public void actionPerformed(ActionEvent event) {
System.out.println("At the tone, the time is " + new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval, listener);
t.start();
}
匿名类不能有构造器,取而代之的是,将构造器参数传递给超类构造器,尤其是在内部类实现接口的时候,不能有任何构造参数。但是括号不可省略。
在匿名内部类中使用方法的参数时,该参数必须被显式声明为final。
使用lambda表达式来写内部类:
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,以便取消产生的引用。注意使用了静态内部类之后,该内部类仍然可以访问到外部类的private static变量和方法。
只有内部类可以声明为static,静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。
如果在静态方法中创建了某个内部类的对象,则这个内部类必须被声明为静态内部类。
与常规内部类不同,静态内部类可以有静态域和方法。
声明在接口中的内部类自动成为static和public类。