内部类,简单的说就是将类定义在另一个类的内部。
<pre name="code" class="java">class Outer
{
...
class Inner
{
...
}
...
}
这里我们将Inner类称为内部类,Outer类称为外部类。
使用内部类的一个主要原因就是内部类可以访问外部类的成员数据,包括私有数据。
public class Test
{
public static void main(String[] args)
{
Outer o = new Outer("Inner class test",2014,true);
o.testInner();
}
}
class Outer
{
private String str_element;
private int int_element;
private boolean bool_element;
public Outer(String str_element,int int_element,boolean bool_element)
{
this.str_element = str_element;
this.int_element = int_element;
this.bool_element = bool_element;
}
//构造内部类对象
public void testInner()
{
Inner in = new Inner();
in.displayElement();
}
//定义内部类
class Inner
{
public void displayElement()
{
System.out.println(str_element);
System.out.println(int_element);
System.out.println(bool_element);
}
}
}
请注意Inner中sysout语句所用到的变量不是定义在Inner自己的作用域中,这些变量是外部类Outer的成员变量,并且访问权限是private的。
javac编译这个源文件,会产生三个class文件,分别是Test.class、Outer.class和Outer$Inner.class。我们只关心Outer.class和Outer$Inner.class这两个类文件。他们分别对应外部类Outer和内部类Inner。编译器自动为内部类产生名字形式为OuterClassName$InnerClassName的类文件。
看到Outer.class和Outer$Inner.class这两个类文件分别对应于外部类和内部类,我们应该可以猜到:这两个class文件对于jvm虚拟机来说,就是两个普通的类文件,与其它类的class文件没有任何差别(就是第二个class文件的名字奇怪一点罢了)。事实也的确如此,内部类只是一种编译器现象,与jvm虚拟机无关。编译器会将上例中定义的Outer和Inner转换成两个普通的类定义,其中外部类Outer被转换成一个新的类Outer(名字没变),内部类Inner被转换成一个新的类名为Outer$Inner(外部类名+$+内部类名)。对于jvm虚拟机来说,完全不知道编译器所做的这些工作,它会像对待其它普通类一样,对待这两个“特殊”的类
好啦!现在我们就来讨论一下,编译器是如何将内部类转换成普通类的。
首先,为了让内部类Inner可以访问外部类Outer的成员变量,编译器会在Inner中增加一个成员变量(用out表示),这个成员变量是Outer类型的引用,指向外部类的对象。
其次,为了将Outer类对象的引用赋值给Inner类的out,还要在Inner类的构造器中新增加一个Outter类型的参数。这样在构造Inner的对象时,就可以将Outer类对象的引用传递给Inner的成员变量out。由于上例的Inner类没有定义构造器,所以编译器自动为其添加一个默认的构造器如下:
public Inner(Outer out)
{
<span style="white-space:pre"> </span>this.out = out;
}
这样当在Outter类的方法中构造Inner类的对象时,就可以通过Inner in = new Inner(this)将Outer类对象的引用传递给Inner。
我们用javap工具查看Outer$Inner类的结果如下:
class Outer$Inner
{
final Outer this$0;
Outer$Inner(Outer);
public void displayElement();
}
可以看出,除了在源文件中定义的displayElement()函数外,编译器又在该类中增加了一个Outer类引用this$0和一个以Outer类型为参数的构造函数。
根据上面得出的这些结论,我们模拟编译器转换内部类的方式,自己手动编写两个被经过转换过的类代码如下:
class Outer
{
private String str_element;
private int int_element;
private boolean bool_element;
public Outer(String str_element,int int_element,boolean bool_element)
{
this.str_element = str_element;
this.int_element = int_element;
this.bool_element = bool_element;
}
public void testInner()
{
Outer$Inner in = new Outer$Inner(this);
in.displayElement();
}
}
class Outer$Inner
{
final Outer this$0;
Outer$Inner(Outer this$0)
{
this.this$0 = this$0;
}
public void displayElement()
{
System.out.println(this$0.str_element); //error
System.out.println(this$0.int_element); //error
System.out.println(this$0.bool_element);//error
}
}
很明显,Outer$Inner类中的displayElement方法中存在错误,原因是str_element等变量是Outer类的private成员,在类外无权访问。那么编译器转换后的类到底是什么样子,可以让内部类访问外部类的私有成员呢?为了解开这个谜团,我们再用javap查看一下编译后的Outer类。
class Outer
{
public Outer(java.lang.String, int, boolean);
public void testInner();
static java.lang.String access$000(Outer); //返回str_element
static int access$100(Outer); //返回int_element
static boolean access$200(Outer); //返回bool_element
}
经过编译器的转换后,新的Outer类中增加了三个新的静态方法,它们的参数都是Outer类的引用类型。这三个静态方法的返回值分别是相应的成员变量。所以经过编译器的转换,Inner类中的变量str_element会转换成Outer.access$000(this$0),这样就可以获取到外部类的私有成员变量了。
最后给出完整的经过编译器转换后的Outer类和Outer$Inner类的形式代码如下:
class Outer
{
private String str_element;
private int int_element;
private boolean bool_element;
public Outer(String paramString, int paramInt, boolean paramBoolean)
{
this.str_element = paramString;
this.int_element = paramInt;
this.bool_element = paramBoolean;
}
public void testInner()
{
Outer$Inner in = new Outer$Inner(this);
in.displayElement();
}
static String access$000(Outer out)
{
return out.str_element;
}
static int access$100(Outer out)
{
return out.int_element;
}
static boolean access$200(Outer out)
{
return out.bool_element;
}
}
class Outer$Inner
{
final Outer this$0;
Outer$Inner(Outer this$0)
{
this.this$0 = this$0;
}
public void displayElement()
{
System.out.println(Outer.access$000(this.this$0));
System.out.println(Outer.access$100(this.this$0));
System.out.println(Outer.access$200(this.this$0));
}
}
总结:本节要重点理解以下内容。
(1).内部类可以访问外部类的数据成员,包括私有数据成员。
(2).内部类与jvm虚拟机无关,编译器会将内部类转换成对应的普通类。说白了内部类机制就是编译器提供的蜜糖,和基本类型的自动打包和自动解包类似,编译器都会对源代码进行一些转换,将内部类的“复杂”代码转换成普通类的形式之后,再编译成class文件。使用内部类在很多情况下可以方便我们的编程。
接下来会继续介绍内部类的其它相关内容,包括匿名内部类、局部内部类、静态内部类等等。