Java内部类

内部类(inner class)是定义在另一个类中的类。使用内部类的原因主要有三个:

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
  • 内部类可以对同一个包中的其他类隐藏起来
  • 当想要定义一个回调函数且不想编写大量代码的时候,可以使用匿名内部类比较方便。

内部类按照不同的应用形式和需求场景分为四种,我在这里将其称之为普通内部类局部内部类匿名内部类静态内部类

内部类的语法比较复杂。内部类是一种编译器现象,在编译过程中,编译器会为其做不少的工作。比如编译器会把内部类翻译成用$分隔外部类名与内部类名的常规类文件,但是jvm对此一无所知。在局部内部类中编译器会为每一个由外部传入的局部变量建立相应的数据域。


普通内部类 使用内部类访问对象状态

在下面的示例代码中,将会体会内部类如何访问外部类对象的状态,此代码实现一个时钟效果,每秒钟会产生一个通告并且鸣出一声

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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{
@Override
public void actionPerformed(ActionEvent e) {
Date now = new Date();
System.out.println("At the tone , the time is "+ now);
if(beep)
Toolkit.getDefaultToolkit().beep();
}
}
}

在外围类的作用域之外,可以这样引用内部类 : OuterClass.InnerClass
eg:

1
2
TalkingClock job = new TalkingClock(1000,true);
TalkingClock.TimePrinter listener = job.new TimePrinter();

在对文件进行编译的过程中,会生成TalkingClock$TimePrinter.class类文件。通过反射技术可以得到如下的大致内容是

1
2
3
4
5
public class TalkingClock$TimePrinter{
public TalkingClock$TimePrinter(TalkingClock);
final TalkingClock this$0;
public void actionPerformed(java.awt.event.ActionEvent);
}

编译器为了引用外围类,生成了一个附加的实例域this$0 (名字this$0是由编译器合成的,在自己的代码中不能够引用它)。另外还可以看到构造器的TalkingClock参数。

在普通的内部类中,除了其外部类知道它的存在外,对其他的包级类都隐藏了起来。


局部内部类

在上面TalkingClock代码中可以发现,内部类TimePrinter这个类名字只在start方法里创建这个类型对象时使用了一次。当遇到这种情况的时候,可以在一个方法里面定义局部内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void start(){
class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
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();
}

局部内部类不能使用public或private访问说明符进行声明。它的作用域被限制在这个声明这个局部内部类的块中。

局部内部类有一个优势,即对外完全隐藏。除了start方法之外,没有任何方法知道TimePrinter类的存在,TalkingClock类也不知道。

和其他内部类相比,局部内部类还有一个优点,他们不仅能够访问包含他们的内部类,还可以访问局部变量。不过这些局部变量需要被声明为final。下例将给出说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void start(int interval ,final boolean beep){
class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
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();
}

在此。我们考察一下start方法的控制流程

  • 调用start方法
  • 调用内部类TimePrinter的构造器,以便初始化对象变量的值
  • 将listener引用传递给Timer构造器,定时器开始计时,start方法结束。此时,start方法的beep参数不复存在。
  • 然后,actionPerformed方法执行if(beep)…。

为了让actionPerformed方法工作,TImePrinter类在beep域释放之前将beep域start方法的局部变量进行备份。
反编译一下TalkingClock$TimePrinter类会得到下述结果:

1
2
3
4
5
6
public class TalkingClock$TimePrinter{
public TalkingClock$TimePrinter(TalkingClock,boolean);
final TalkingClock this$0;
final boolean val$beep;
public void actionPerformed(java.awt.event.ActionEvent);
}

注意构造器的boolean参数和val$beep实例变量。当创建一个对象的时候,beep就会被传递给构造器,并储存在val$beep这个final域里面。在start方法里面将beep参数声明成final,对它进行初始化后不能够在此修改。因此,就使得局部变量和局部类内建立的拷贝保持一致。


匿名内部类

将局部内部类再往下深入一步,假如只创建这个类的一个对象,就不需要命名了。此时类被称为匿名内部类。
匿名内部类的语法如下所示:

new SuperType(construcation parameters){


innner class method and data


}

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void start(int interval ,final boolean beep){
ActionListener listener = new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
Date now = new Date();
System.out.println("At the tone , the time is "+ now);
if(beep)
Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval,listener);
t.start();
}


静态内部类

有时候使用内部类只是只是为了将类隐藏在另一个类里面,而不需要内部类去引用外围类对象的。此时,可以将内部类声明为static,以取消对外围类的引用。将TimePrinter类中去掉外围类的beep对象变量,就可以将TimePrinter声明为static。

1
2
3
4
5
6
7
8
public static class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
Date now = new Date();
System.out.println("At the tone , the time is "+ now);
Toolkit.getDefaultToolkit().beep();
}
}

注意:只有内部类可以声明为static && 声明在接口中的内部类自动成为static和public类