内部类是在另一个类中定义的类
使用内部类访问对象状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public class TalkingClock {
private int interval;
private boolean beep;
public TalkingClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
}
public void start() {
(new Timer(interval, new TimePrinter())).start();
}
public class TimePrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (beep) {
Toolkit.getDefaultToolkit().beep();
}
}
}
}
|
Java中,创建的对象存在内存堆区,当一个外部类对象创建一个内部类时,两个对象的内存区域时独立的,但是内部类的对象中总有一个隐式引用,指向创建它的外部类对象,这样在内部类需要访问外部类的属性的时候,会使用这个隐式引用找到外部类对象,然后访问到外部类对象的属性。
内部类可以是私有的,只能通过外部类方法创建
内部类的特殊语法规则
this 表示当前内部类引用
OutClass.this 表示外围类引用
OutClass.InnerClass 在外围类的之外,可以使用这种方式引用内部类,前提是内部类是非private
内部类是否有用、必要和安全
内部类在编译后使用$
符号分隔,如上文的TalkingClock$TimePrinter
对JVM来说,内部类和常规类是一样的,区别就在于是否有$分隔符, 那么内部类可以访问外围类中的私有变量这个特权又是怎么实现的呢?
通过ReflectTest程序查看TalkingClock类可以看到:
1
2
3
4
5
6
7
8
9
| class TalkingClock{
private int interval;
private boolean beep;
public TalkingClock(int, boolean);
static boolean access$0(TalkingClock);
public void start();
}
|
编译器在外围类中添加了静态方法access$0
,这个方法的返回作为内部类的beep
,内部类在访问beep属性的时候,就是调用了access$0方法。
安全性问题:虽然access$0
不是合法的Java 方法名,但是可以使用16进制编辑器创建一个用虚拟机指令调用那个方法的类文件。
局部内部类
声明在方法中的内部类,作用域为当前块{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class TalkingClock {
private int interval;
private boolean beep;
public TalkingClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
}
public void start() {
class TimePrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (beep) {
Toolkit.getDefaultToolkit().beep();
}
}
}
(new Timer(interval, new TimePrinter())).start();
}
}
|
由外部方法访问变量
看个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public class TalkingClock {
public void start(int interval,boolean beep) {
class TimePrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (beep) {
Toolkit.getDefaultToolkit().beep();
}
}
}
ActionListener listener = new TimePrinter();
Timer timer = (new Timer(interval,listener));
timer.start();
}
}
|
这里TalkingClock
的start
方法是延迟执行的,在计时结束之后beep变量就已经不在了,所以这里内部类有一个机制,就是当访问局部变量的时候,在局部变量释放之前会进行备份。
通过ReflectTest查看Time-printer:
1
2
3
4
5
6
7
8
| class TalkingClock$1TimePrinter{
TalkingClock$1TimePrinter(TalkingClock,boolean);
public void actionPerformed(java.awt.event.ActionEvent)
final boolean val$beep;
final TalkingClock this$0;
}
|
这里将beep定义为final,确保局部变量和局部类中建立的拷贝一致。如果尝试在局部类中修改这个局部变量,将会报错:
在这种情况下IDEA就算按Alt + Enter也没有代码修补建议。
如果要修改,则需要先将从外围类中得到的局部变量保存在内部类中:
1
2
3
4
5
6
7
8
9
10
11
| class TimePrinter implements ActionListener {
private boolean tmp = beep;
@Override
public void actionPerformed(ActionEvent actionEvent) {
tmp = true;
if (tmp) {
Toolkit.getDefaultToolkit().beep();
}
}
}
|
总之就是,内部类可以访问但是不能修改从外部类中得到的局部变量。
匿名内部类
一般用于只创建这个类的一个对象的时候。
举个例子:
new Thread(...).run
深入一点:
1
2
3
4
5
6
| ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// code
}
};
|
上面例子里面表示的是一个实现了ActionListener接口的匿名内部类的一个对象。
匿名内部类的具体格式为:
1
2
3
| new 类名/接口名 (参数列表){
// 需要实现的方法
}
|
由于匿名内部类没有名字,所以不含有构造器,取而代之的是将构造器传给超类,上述参数列表就是超类的构造器列表。
花括号内可以进行方法和属性的定义:
1
2
3
4
5
6
7
8
9
10
11
| ActionListener listener = new ActionListener() {
private int testInt;
public void testFun(){
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
// code
}
};
|
PS:我想了一下,在匿名内部类中定义公有域是没有意义的,因为没有名字,也就无法通过这个匿名内部类的一个对象引用来访问公有域。一般我们写这样的匿名类,会将这个匿名类的对象赋给其超类对象引用,这样其实只能访问其超类中的域,所以在匿名内部类中写的域是给自身调用的。
初始化块
1
2
3
4
| ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("hello");
arrayList.add("hi");
test(arrayList);
|
等价于
1
2
3
4
5
6
| test(new ArrayList<String>(){
{
add("hello");
add("hi");
}
});
|
用到的是类初始化中的初始化块,在声明了一个匿名内部类后,外层花括号定义了内部类体,内层花括号定义了初始化块,表示在当前内部类中add
两个String
静态内部类
除了不能访问外围类的域之外和内部类完全一样,这里的静态有点全局的意思,可以实例化:
OuterClass.StaticClass