Java内部类(一)

内部类,简单的说就是将类定义在另一个类的内部。

<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文件。使用内部类在很多情况下可以方便我们的编程。

接下来会继续介绍内部类的其它相关内容,包括匿名内部类、局部内部类、静态内部类等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值