内部类:顾名思义,就是在类的内部定义的类,内部类的分类可以分为:成员内部类,局部内部类,匿名内部类,静态内部类,下面一个一个攻破,
在外部类里面创建成员内部类的实例:this.new B();
在外部类之外创建内部类的实例:(new Outer()).new Inner().go();
在内部类里访问外部类的成员:
Outer.this.member
成员内部类:
举个例子:
package test10;
public class Outer {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 成员内部类
* @author admin
*
*/
class Inner{
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public void print(){
name="jack"; //使用外部类的成员变量
System.out.println(str+" "+name);
}
}
public Outer() {
super();
}
public Outer(String name) {
super();
this.name = name;
}
public static void main(String[] args) {
Outer outer = new Outer();
//创建内部类(需要先创建外部类)
Outer.Inner inner = outer.new Inner();
inner.str ="hello world";
inner.print();
}
}
从上面的代码可以看出,内部类访问了外部类的成员变量,内部类之所以能够访问外部类的成员变量,是因为在内部类对象中存在指向外部类对象的引用。可以这个引用是怎么来的呢?这个要归功于我们的编译器,进入到test10目录下,在命令行中输入如下的命令:
javap -classpath . -v Outer$Inner
执行命令后会输出一系列的内容,其中有部分是这样的:
final test10.Outer this$0;
descriptor: Ltest10/Outer;
flags: ACC_FINAL, ACC_SYNTHETIC
test10.Outer$Inner(test10.Outer);
descriptor: (Ltest10/Outer;)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #12 // Field this$0:Ltest10/Outer;
5: aload_0
6: invokespecial #14 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 17: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Ltest10/Outer$Inner;
从第一行中我们得知:在内部类Outer$Inner中, 存在一个名字为this$0 , 类型为Outer的成员变量, 并且这个变量是final的。 其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用”。但是我们在定义这个内部类的时候, 并没有声明它, 所以这个成员变量是编译器加上的。 那么是如何给这个变量赋值的呢?我们接着往下看:第5行是内部类的构造方法,我们知道, 如果在一个类中, 不声明构造方法的话, 编译器会默认添加一个无参数的构造方法。 但是这句话在这里就行不通了, 因为我们明明看到, 这个构造方法有一个参数, 并且类型为Outer。 所以说, 编译器会为内部类的构造方法添加一个参数, 参数的类型就是外部类的类型。
下面我们看看在构造参数中如何使用这个默认添加的参数。 我们来分析一下构造方法的字节码。 下面是每行字节码的意义:
aload_0 :
将局部变量表中的第一个引用变量加载到操作数栈。 这里有几点需要说明。 局部变量表中的变量在方法执行前就已经初始化完成;局部变量表中的变量包括方法的参数;成员方法的局部变量表中的第一个变量永远是this;操作数栈就是执行当前代码的栈。所以这句话的意思是: 将this引用从局部变量表加载到操作数栈。
aload_1:将局部变量表中的第二个引用变量加载到操作数栈。 这里加载的变量就是构造方法中的Outer类型的参数。
putfield #10 // Field this$0:LOuter;
使用操作数栈顶端的引用变量为指定的成员变量赋值。 这里的意思是将外面传入的Outer类型的参数赋给成员变量this$0 。 这一句putfield字节码就揭示了, 指向外部类对象的这个引用变量是如何赋值的。
到这里,应该对内部类是如果访问到外部类的成员变量的有了一个比较清晰的认识 ,下面对这一部分简单的做一个总结:
1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;
2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;
3 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。