Java基础系列(三十):局部内部类,匿名内部类

时间:2022-06-17
本文章向大家介绍Java基础系列(三十):局部内部类,匿名内部类,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

What

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

Why

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

How

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

public class InnerClassTest {
    private Integer times;
    private boolean beep;
    
    public InnerClassTest(Integer times, boolean beep) {
        this.interval = interval;
        this.beep = beep;
    };
    
    public void start(){
        class TestListener implements ActionListner {
            public void actionPerformed(ActionEvent event) {
                System.out.println("TestListener is running");
                if (beep) {
                    Tookit.getDefaultToolkit().beep();
                }
            }
        }
        ActionListener listener = new TestListener();
        Timer t = new Timer(times, listner);
        t.start();
    }    }

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

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

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

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

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

public void start(int times, boolean beep){
    class TestListener implements ActionListner {
        public void actionPerformed(ActionEvent event) {
            System.out.println("TestListener is running");
            if (flag) {
                Tookit.getDefaultToolkit().beep();
            }
        }
    }
    ActionListener listener = new TestListener();
    Timer t = new Timer(times, listner);
    t.start();}  

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

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

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

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

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

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

匿名内部类

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

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

public void start(int times, boolean beep){  
    ActionListener listener = new ActionListener() {
        public void actionPerformed(ActionEvent event) {
            System.out.println("TestListener is running");
            if (flag) {
                Tookit.getDefaultToolkit().beep();
            }
        }
    }
    Timer t = new Timer(times, listner);
    t.start();}  

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

通用的语法格式是:

new SuperType(constrution params) {
    inner class methods and data
}

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

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

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

静态内部类(仅供了解)

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

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

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


下节预告

有关内部类的知识,我们就学习到这里,这些已经足够我们日常开发使用了。下一节我们来学习有关异常的知识~