首先我们来看看使用内部类的一个例子。
interface INormalInner{
void normalInner();
}
public class NormalInnerClass {
private String outterClassStr="NormalInnerClass";
class INormalInnerImpl implements INormalInner{
@Override
public void normalInner() {
System.out.println("hello world,i am inner class .i want to invoke the string of outter class "+outterClassStr);
}
}
public static void main(String[] args) {
NormalInnerClass out=new NormalInnerClass();
INormalInner inner=out.new INormalInnerImpl();
inner.normalInner();
}
}
这段内部类的代码很简单,简单在内部类中输出外部类的一个成员变量的值。我们都知道,NormalInnerClass类属于外部类,而INormalInnerImpl类属于NormalInnerClass类的内部类,要创建内部类的实例,必须要用外部类的实例new出来,这样内部类可以拿到对应的外部类的实例,进而可以调用外部类的方法和成员。我们也知道,在这个文件经过编译之后,会生成3个class文件。分别是INormalInner.class,NormalInnerClass.class,NormalInnerClass$INormalInnerImpl.class,接口的class,外部类的class,内部类的class。
我们知道内部类实例拿到了一个外部类实例对象引用,然后通过引用进而可以在内部类方法中调用外部类实例的成员和方法,看起来很是顺其自然,但是大家有没有想过,就拿上面的例子来说,外部类NormalInnerClass的成员变量outterClassStr是私有的,那在另一个内部类的class文件中拿到外部类的实例引用,但是通过引用怎么可以访问到私有的成员和方法的呢?
决定使用javap反编译外部类的class文件,到底是怎么一回事?
命令:
javap -p NormalInnerClass.class
Compiled from "NormalInnerClass.java"
public class normal.NormalInnerClass {
private java.lang.String outterClassStr;//成员变量
public normal.NormalInnerClass();//构造函数,因为我们没有定义构造函数,所以编译器生成一个默认的构造函数
public static void main(java.lang.String[]);//静态方法,程序入口
static java.lang.String access$0(normal.NormalInnerClass);//我们定义过这个方法
}
发现很神奇的事情。 static java.lang.String access$0(normal.NormalInnerClass);这个静态方法我从来没有定义过啊,怎么会有这个呢?其实这个是编译器自己生成的,主要的作用就是解决我们上面跑出的问题,用这个静态的方法来达到使用外部类对象访问外部类的私有成员和方法,方法的参数就是传入外部类对象的引用,然后就是在外部类的代码里面调用对象调用私有成员的方法,这样就顺利成章啦。那是不是真的在内部类里面调用外部类对象调用私有成员和方法时会调用这种静态函数呢?我们来javap一下内部类里面的代码实现。
命令:
javap -c NormalInnerClass\$INormalInnerImpl.class
Compiled from "NormalInnerClass.java"
class normal.NormalInnerClass$INormalInnerImpl implements normal.INormalInner {
final normal.NormalInnerClass this$0;
normal.NormalInnerClass$INormalInnerImpl(normal.NormalInnerClass);
Code:
0: aload_0
1: aload_1
2: putfield #12 // Field this$0:Lnormal/NormalInnerClass;
5: aload_0
6: invokespecial #14 // Method java/lang/Object."<init>":()V
9: return
public void normalInner();
Code:
0: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #28 // class java/lang/StringBuilder
6: dup
7: ldc #30 // String hello world,i am inner class .i want to invoke the string of outter class
9: invokespecial #32 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
12: aload_0
13: getfield #12 // Field this$0:Lnormal/NormalInnerClass;
16: invokestatic #35 // Method normal/NormalInnerClass.access$0:(Lnormal/NormalInnerClass;)Ljava/lang/String;
19: invokevirtual #41 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #45 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: invokevirtual #49 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: return
}
我们重点看第16行这句话:invokestatic #35 // Method normal/NormalInnerClass.access$0:(Lnormal/NormalInnerClass;)Ljava/lang/String;是不是发现了调用了NormalInnerClass.access$0这个静态方法,哦,终于明白了,原来内部类是这样访问外部类实例的成员和方法的,那外部类实例访问内部类的成员和方法是不是也是通过这种方式,调用内部类的静态的access$0这类的方法呢?答案就是:of coures。大家可以试试哦。
那静态内部类呢?内部类怎么访问外部类的私有的静态的方法和成员呢?我们依然来做个代码测试一下。
代码如下:
interface INormalInner{
void normalInner();
}
public class NormalInnerClass {
private String outterClassStr="NormalInnerClass";
class INormalInnerImpl implements INormalInner{
@Override
public void normalInner() {
System.out.println("hello world,i am inner class .i want to invoke the string of outter class "+outterClassStr);
printNormal();
}
}
private static void printNormal(){
System.out.println("i am in outter class,i am a private method.");
}
public static void main(String[] args) {
NormalInnerClass out=new NormalInnerClass();
INormalInner inner=out.new INormalInnerImpl();
inner.normalInner();
}
}
下面反编译一下NormalInnerClass外部类的class文件。
命令:
javap -p NormalInnerClass.class
Compiled from "NormalInnerClass.java"
public class normal.NormalInnerClass {
private java.lang.String outterClassStr;
public normal.NormalInnerClass();
private static void printNormal();
public static void main(java.lang.String[]);
static java.lang.String access$0(normal.NormalInnerClass);
static void access$1();
}
咦,又生成了一个我们没有见过的代码 static void access$1();。哦,原来私有的静态调用也是这么回事。
那可能有这么一种需求,就是外部类实例不能访问内部类实例的成员和方法,怎么来呢?上面说了,普通的内部类和静态内部类都是可以通过access$1()这类方法获得,那怎么搞呢?其实很简单,就是使用内部类,想啊,内部类没有名字,也就拿到到内部类真正类型的引用了,那怎么调用内部类引用的成员和方法呢,也就没有必要生成静态的这些access$1()方法了。就算我们拿到了内部类的引用,但是这个引用是内部类实现的接口的引用,或者抽象类的引用,当然只能操作接口的方法,抽象类的方法,而在内部类中增加的东西是访问不到的,这个应该一样就明白。
就像下面这个代码:
private static void printNormal(){
System.out.println("i am in outter class,i am a private method.");
INormalInner inner=new INormalInner() {
//这个成员变量,只能在内部类中访问到,外部类中无法访问到
String innerStr="inner class";
@Override
public void normalInner() {
System.out.println(innerStr);
}
};
inner.normalInner();
}
内部类的String innerStr="inner class”;这个变量无法在外部类中进行访问到,接口引用inner是访问不到的。
好啦,java内部类的底层实现就是这么多啦,下一篇和大家讲讲lambda表达式的底层实现。