What
内部类的定义很简单,正如它的名字所言,它是定义在另一个类中的类。
Why
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码的时候,使用匿名内部类比较便捷。
How
Just A Demo First :
public class InnerClassTest {
private Integer times;
private boolean beep;
public InnerClassTest(Integer times, boolean beep) {
this.interval = interval;
this.beep = beep;
};
public void start(){
ActionListener listener = new TestListener();
Timer t = new Timer(times, listner);
t.start();
}
public class TestListener implements ActionListner {
public void actionPerformed(ActionEvent event) {
System.out.println("TestListener is running");
if (beep) {
Tookit.getDefaultToolkit().beep();
}
}
}
}
我们回过头来分析这个类,首先这里的TestListener类位于InnerClassTest类内部,但是这并不意味着每个InnerClassTest类都有一个TestListener实力域,我们可以在start()
方法中看到,这个内部类对象是通过一个构造器来构造的ActionListener listener = new TestListener();
,我们接着观察这个内部类,发现他其中使用了一个名叫beep的变量,但是这个类中并没有任何关于这个beep变量的定义,通过观察我们可以发现,这个名叫beep的变量来自于它的外围类对象的数据域。
可能有些同学会感到奇怪,为什么内部类可以拿到外围类的对象呢?
实际上,内部类的对象有一个隐式引用,它指向了创建它的外部类对象,这个引用在是在内部类默认的构造器中去设置的,我们如果没有为内部类定义构造器,编译器就会去给这个内部类添加一个默认的构造器,类似于这样的:
public TestListener(InnerClassTest t) {
//这里只是一个伪代码,实际上编译器生成的并不是这样的。这里只是为了说明原理。
outer = t;
}
当我们在start方法中实例化了这个内部类的时候,编译器就会去把外围类的this引用传递给当前的内部类:
//这时编译器所做的工作,这里只是一个伪代码,并非实际代码就是如此
ActionListener listener = new TestListener(this);
而在这个内部类中使用外围类变量的时候,完整的编写方式其实是这样的:
public void actionPerformed(ActionEvent event) {
System.out.println("TestListener is running");
if (InnerClassTest.this.beep) {
Tookit.getDefaultToolkit().beep();
}
}
这样,我们就可以很清楚的知道,这个flag的值是怎么从外围类一步一步的走到了内部类的方法中并加以使用的。
反过来,可以采用下列语法格式去编写实例化的语句可能会更为直观,易读:
ActionListener listener = this.new TestListener();
在这里,最新构造的TestListener对象的外围类引用被设置为创建内部类对象的方法中的this引用。通常,this的限定词是多余的,我们可以把它省略掉,但是可以通过显式地命名将外围类引用设置为其他对象:
InnerClassTest otherObj = new InnerClassTest();
InnerClassTest.TestListener listener = otherObj.new TestListener();
这里需要注意一点,我们可以在内部类中声明一个静态域,但是这个静态域必须是final关键词来修饰的,因为我们希望一个静态域只有一个实例,不过对于每个外部对象,都分别会有一个单独的内部类实例,如果这个域不是final,那么它就不是唯一的。
深入了解内部类
内部类是一种编译现象,与虚拟机无关。编译器会把内部类翻译成用$符号分隔外部类名和内部类名的常规类文件,而虚拟机对此一无所知,它仍然会认为内部类与其他的类并无很大的区别。
我们使用java -p private
对刚刚那个类生成的内部类文件进行反编译后可以发现:
public class InnerClassTest$TestListener {
public InnerClass$TestListener(InnerClassTest);
public void actionPerformed(java.awt.event.ActionEvent);
final InnerClassTest this$0;
}
可以看到,编译器为了医用外围类,生成了一个附加的实例域this$0,另外,还可以看到构造器的外围类参数。
接下来,我们来继续来深入,为什么内部类可以访问外围类的私有数据,它的访问特权来源于何处,我们对外围类进行反编译:
class InnerClassTest {
private Integer times;
private boolean beep;
public InnerClassTest(Integer times, boolean beep);
static boolean access$0(InnerClassTest);
public void start();
}
我们可以发现,编译器在外围类给我们添加了一个静态方法 access$0,它将返回作为参数传递给它的对象域beep,之后内部类调用那个方法,就可以去访问到外围类的私有域。
公众号
扫码或微信搜索 Vi的技术博客,关注公众号,不定期送书活动各种福利~