内部类

Posted by     "Eric" on Saturday, March 7, 2020

之前看过很多遍内部类,但总对其概念比较模糊,这次对其进行一个总结。

1. 内部类的特权

内部类指的是在一个类的内部所定义的类,类名不需要和源文件名相同。在Java中,内部类是一个编译概念,一旦编译成功,内部类和外部类就会成为两个完全不同的类。

但是,内部类会享有一些特权。在内部类中可以外部类的成员变量,即使该成员变量被声明为私有的,其原因是编译器给内部类添加了一个成员变量,该变量持有外部类对象的引用,并且修改了内部类所有的构造器,添加了对外围类引用的参数。但这还不能实现访问外围类私有变量的效果,为了能够访问外围类的私有成员变量,在外围类中也声明了一个静态方法,该类需要一个外围类对象作为参数,内部类通过调用外围类的这个静态方法,获得静态类的私有成员变量。

以下面这个类为例,进行说明

class Outter{
    private int a;
    public void setA(int a){
        this.a=a;
    }
    class Inner{
        public int getPrivate(){
            return Outter.this.a; //访问了外部类的私有变量,这是合法的
        }
    }
}

public static void main(String[] args) {
    Outter outter=new Outter();
    outter.setA(2);
    Outter.Inner inner=outter.new Inner(); //在外围类外部定义内部类,需要向内部类传入外围了对象这一参数
    System.out.println(inner.getPrivate());//合法,output=2;
}
//上面这种定义经过编译后,在JVM看来是等同于下面的类定义的
class Outter{
    private int a;
    public void setA(int a){
        this.a=a;
    }
    static int getA(Outter outter){  //为了使内部类能够访问外围类的私有变量
        return outter.a;             //相当于在外围类中定义了一个静态方法
    }							  //另外,这里还用到了一个知识点
}                     		    //一个方法可以访问所属类的所有对象的私有数据

class Inner{
    final Outter outter;		//成员变量持有外围类一个对象的引用
    public Inner(Outter outter){//内部类的构造方法需要传入外围类对象的引用
        this.outter=outter;
    }
    public int getPrivate(){
        return Outter.getA(outter);	//调用外围类的静态方法
    }
}

public static void main(String[] args) {
    Outter outter=new Outter();
    outter.setA(2);
    Inner inner=new Inner();
    System.out.println(inner.getPrivate());
}

总结起来,内部类享有特权有两个原因:

  1. 内部类持有外围类对象的引用
  2. 外围类中有静态方法可以返回私有成员变量

2. 局部内部类

局部内部类被定义被定义在方法中,不能用publicprivate访问说明符进行声明,它的作用域被限定在声明这个局部类的块中,外部世界完全不知道它的存在,即使是外围类中其他的代码,也不能访问他。他不仅具有普通内部类的特权——访问外围类的所有成员变量,还由另一个特权能够访问局部变量。如下面这个例程

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();
}

在内部类TimePrinter中使用了外围方法中的局部变量beep,这看起来没有什么大惊小怪的,但是我们仔细考虑下程序控制流程

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

为了能够让内部类中actionPerfomed方法正常工作,TimePrinter类在beep域释放之前对其在内部类中进行了备份,而且被声明为了final类型的,如下

class TalkingClock$TimePrinter{
    TalkingClock$TimePrinter(TalkingClokck,boolean);
    //构造方法中传入局部变量和外围类对象
    public void actionPerformed(java.awt.event.ActionEvent);
    
    final boolean val$beep; //为局部变量 创建了一个成员变量
    final TalkingClock this$0;//成员变量用来接收外围类对象
}

3. 匿名内部类

可以将局部内部类再推进一步,如果只创建这个类的对象一次,我们就不需要对这个类进行命名,这种类被称为匿名内部类,其语法格式为

new SuperType(construction parameters){
    inner class methods and data;
}

由于构造器的名字必须与类名相同,而匿名类没有名字,因此匿名类不能有构造器。取而代之的,将构造器参数传递给超类的构造器,尤其是在内部类实现接口的时候,不能有任何参数。因此定义匿名内部类的前提是,内部类必须继承一个类或者实现接口,而且所创建的也是匿名子类对象

如果构造参数的闭小括号后面跟一个开大括号,正在定义的就是匿名内部类,由此可以继续延伸,有一种技巧是双括号初始化,例如

ArrayList<String> friedns = new ArrayList<>();
friends.add("Harry");
friends.add("Tony");
invite(friends);
//以上步骤可以简写为
invite(new ArrayList<>(){{add("Harry");add("Tony");}});
//外括号建立了ArrayList的一个匿名子类。内括号则是一个对象的构造块。

4. 静态内部类

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。另外,如果该类如果需要在外围类的静态方法中使用,必须被定义为静态内部类,因为外围类中没有产生对象,而内部类的创建需要传入外围类的对象。