1. 内部类的特点
Java内部类就是在一个类中定义另一个类,特点在于:
- 内部类的方法可以访问外部类的变量,即使是外部类的私有变量。
- 内部类能够对同一个包下的其它类隐藏。
2. 内部类使用示例
public class Outter {
private boolean flag = true;
public void init() {
this.new Inner().innerDo();
}
public static void main(String[] args) {
new Outter().init();
}
class Inner {
void innerDo() {
if (Outter.this.flag) {
System.out.println("hello world");
}
}
}
}
非静态内部类对象的创建方式是通过外部类的对象创建的,如上例所示,创建可以为new Outter().new Inner()。
在非静态内部类对象里面使用外部类的成员变量,可以直接使用或者如Outter.this.flag的方式。
3. 内部类原理
为什么内部类可以访问外部类的私有变量?这样是否会带来安全问题?这需要我们深入了解内部类的构造。内部类的解析时编译器变换处理,而非Jvm去特殊处理。
首先,编译上述外部类
javac Outter.java
可以看到产生了两个新文件Outter.class和Outter$Inner.class。这说明内部类也是被编译成为了一个字节码文件。
反编译内部类
javap -private Outter\$Inner.class
因为笔者是linux环境,所以添加了转义符\
Compiled from "Outter.java"
class Outter$Inner {
final Outter this$0;
Outter$Inner(Outter);
void innerDo();
}
可以看到编译器自动帮我们添加了外部类的成员变量,因此内部类对象持有外部类对象的引用。
反编译外部类
javap -private Outter.class
Compiled from "Outter.java"
public class Outter {
private boolean flag;
public Outter();
public void init();
public static void main(java.lang.String[]);
static boolean access$000(Outter);
}
可以看到编译器自动添加了一个静态方法access$000(Outter)方法。我们具体看一看这个方法。
javap -v Outter.class
截取出该方法的汇编语句
Constant pool:
#1 = Fieldref #6.#25 // Outter.flag:Z
static boolean access$000(Outter);
descriptor: (LOutter;)Z
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field flag:Z
4: ireturn
LineNumberTable:
line 7: 0
翻译过来就是
static boolean access$000(Outter outter) {
return outter.flag; // 等同于这个效果,实际这样写会编译报错。
}
最后我们看看innerDo()方法是怎样的
javap -v Outter\$Inner.class
Constant pool:
#1 = Fieldref #7.#20 // Outter$Inner.this$0:LOutter;
void innerDo();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field this$0:LOutter;
4: invokestatic #3 // Method Outter.access$000:(LOutter;)Z
7: ifeq 18
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: ldc #5 // String hello world
15: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: return
LineNumberTable:
line 21: 0
line 22: 10
line 24: 18
StackMapTable: number_of_entries = 1
frame_type = 18 /* same */
可以看到内部方法取用flag成员变量的方式是使用内部类对象隐式的Outter对象,调用外部类隐式的静态方法取用。
至此,java非静态内部类的原理就一目了然了。