What

局部内部类就是定义在某个方法内部的内部类,它的作用域仅限于这个方法。

Why

当我们在外围类中定义的内部类仅仅在某个方法中使用了一次,这种情况下,我们就可以选择去使用局部内部类。

How

以上节课的例子继续讲解,由于TestListener这个内部类仅仅在start方法中使用了一次,所以我们在这里可以使用局部内部类。

  1. public class InnerClassTest {
  2. private Integer times;
  3. private boolean beep;
  4. public InnerClassTest(Integer times, boolean beep) {
  5. this.interval = interval;
  6. this.beep = beep;
  7. };
  8. public void start(){
  9. class TestListener implements ActionListner {
  10. public void actionPerformed(ActionEvent event) {
  11. System.out.println("TestListener is running");
  12. if (beep) {
  13. Tookit.getDefaultToolkit().beep();
  14. }
  15. }
  16. }
  17. ActionListener listener = new TestListener();
  18. Timer t = new Timer(times, listner);
  19. t.start();
  20. }
  21. }

这里需要注意,局部类不可以使用public或者private访问修饰符进行声明,因为它作用域仅仅被限定在声明这个局部类的块中。

局部类有一个优势,它可以对外部世界完全的隐藏,即使他的外部类中的其他模块也不可以访问它,除了start方法以外,没有任何方法知道这个内部类的存在。

外部方法访问变量(进阶)

与其他的内部类相比,局部类还有一个其他内部类所不具备的有优点。它不仅可以访问包含它们的外部类,还可以访问局部变量,但是这些局部变量必须声明为final,它们一旦被赋值,就不能被改变。

下面我们接着来改变上面的那个栗子:

  1. public void start(int times, boolean beep){
  2. class TestListener implements ActionListner {
  3. public void actionPerformed(ActionEvent event) {
  4. System.out.println("TestListener is running");
  5. if (flag) {
  6. Tookit.getDefaultToolkit().beep();
  7. }
  8. }
  9. }
  10. ActionListener listener = new TestListener();
  11. Timer t = new Timer(times, listner);
  12. t.start();
  13. }

我们可以看到,外围类不在需要去存储实例变量beep了,它只是引用start方法中的参数。
接下来我们来深入了解这个方法的控制流程:

  1. 调用start方法
  2. 调用内部类的构造器,初始化对象变量listener
  3. listener引用传递给Timer构造器,定时器开始计时,start方法结束。此时,start方法中beep变量被回收。
  4. 然后actionPerformed方法执行if(beep)

看到这里,我相信大部分人会有疑问,为什么beep变量被回收,但是actionPerformed方法仍然可以调用到这个方法?

实际上,内部类在beep域被释放之前将beep域用start方法中的局部变量进行备份,我们接下来来看一下反编译后的内部类,来证实我们的猜测:

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

请注意构造器的boolean参数和val$beep实例变量。当创建一个对象的时候,beep就会传递给构造器,并存储在val$beep域中。编译器必须检测对局部变量的访问,为每一个变量建立相应的数据域,并将局部变量拷贝到构造器中,以便将这些数据域初始化为局部变量的副本。

匿名内部类

匿名内部类其实就是对局部内部类的一个深化的应用,如果我们只是需要创建这个类的一个对象,那么我们完全不必去给这个类命名,这种类就被称为匿名内部类。

接下来,我们接着对上面的例子进行改编:

  1. public void start(int times, boolean beep){
  2. ActionListener listener = new ActionListener() {
  3. public void actionPerformed(ActionEvent event) {
  4. System.out.println("TestListener is running");
  5. if (flag) {
  6. Tookit.getDefaultToolkit().beep();
  7. }
  8. }
  9. }
  10. Timer t = new Timer(times, listner);
  11. t.start();
  12. }

这段语句的含义是:创建一个实现ActionListener接口的类的新对象,需要实现的方法定义在括号内。

通用的语法格式是:

  1. new SuperType(constrution params) {
  2. inner class methods and data
  3. }

其中SuperType既可以是接口,那么内部类就要去实现这个接口,它同样可以是一个类,那么内部类就要去扩展它。

由于构造器的名字必须与类名相同,但是匿名类并没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给父类构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。

如果构造参数的闭小括号后跟的是单引号,那么就是在构造一个类的新对象,如果说构造参数的闭小括号后面跟一个开大括号,正在定义的就是匿名内部类。

静态内部类(仅供了解)

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外部类对象。所以可以把内部类声明为static,以便取消产生的引用。

只有内部类可以声明为static,静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。

与常规内部类不同的地方是,静态内部类可以有静态域和方法,声明在接口中的内部类自动生成static和public类。


公众号

扫码或微信搜索 Vi的技术博客,关注公众号,不定期送书活动各种福利~

Java基础系列(三十):局部内部类 - 图1