xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
该篇文章涉及到了java可访问特性以及内部类的一些内容,向读者展示了一个内部类的特别的现象,通过这个例子,使开发者了解到一些他们以前可能没有注意到的细节,也许可以帮助开发者更透彻的了解java的可访问特性、内部类和虚拟机。并且通过对这些细节的分析,可能会对开发者思考、分析问题,以及适当的使用工具有所启迪。
java的访问修改符(access modifier)包括:default-access、public、private、protected四种。并不是所有的情况都可以使用全部四种访问修改符,有的情况下使用某些访问修改符是没有意义的,例如:如果一个类不是内部类,则不能使用private作为该类的访问修改符,编译下面的代码:
private class test
{ ... }
jdk编译器将给出“modifier private not allowed here”错误提示。因为private特性只能由定义它的那个类使用,如果上面的代码通过编译,则不会有任何情况可以使用test类,那么也就不会有任何意义。
如果一个类没有定义任何构造函数,则编译器将生成一个缺省的构造函数,该构造函数的访问修改符和类的访问修改符相同,例如:
class test将生成test()构造函数public class test将生成public test()构造函数。
在使用内部类的情况,上述的特性将使编译器表现出一个特别现象。需要说明的是,下面的例子仅针对windows系统下jdk编译器,作者并没有尝试使用其他的编译器的情况。但由于java编译器生成的是class文件这种中间形式的代码,所以下面的讨论应该适用于任何符合java标准的编译器。
编译下面的代码:
public class wrapper
{
private class innerclass
{}
private void testinnerclass()
{
innerclass inner = new innerclass();
}
public static void main(string[] args)
{
wrapper wrapper = new wrapper();
wrapper.testinnerclass();
}
}
将产生三个class文件:wrapper、wrapper$innerclass和wrapper$1。
对于前两个文件,了解内部类的读者都会理解,但第三个类wrapper$1的作用是什么呢?
使用java的反射机制,或者使用javap反汇编器,将发现wrapper$1类没有任何成员变量和方法,而wrapper$innerclass则除了有一个private wrapper$innerclass()构造方法外,还有一个wrapper$innerclass(wrapper$1)构造方法,使用javap,你将发现wrapper$innerclass(wrapper$1)并没有使用wrapper$1类型的参数,而只是直接调用了private wrapper$innerclass()。如果读者仔细思考一下创建一个新的类实例的过程,大概已经明白了产生上述现象的原因:当程序试图创建一个wrapper$innerclass的类实例时,却不能使用其缺省的构造函数,因为wrapper$innerclass()是private的,不能由外部使用。因此编译器不得不再生成一个可访问的构造函数,由于这里只有wrapper类的private void testinnerclass()方法使用了new innerclass(),所以编译器只(需)为这个新的构造函数生成了default-access的访问修改符。同时,为了和已有的缺省构造函数有所区别,就加入了一个wrapper$1类型的参数,为此,编译器还要生成一个wrapper$1类。
为了更简单,(也许)更清晰的看到编译器生成的class代码工作的原理,读者可以使用java反编译器,来看看class反编译后生成的java源程序,下面是作者使用jad反编译后生成的wrapper类的代码:
// decompiled by jad v1.5.7d. copyright 2000 pavel kou.netsov.
// jad home page: http://www.geocities.com/siliconvalley/bridge/8617/jad.html
// decompiler options: packimports(3)
// source file name: wrapper.java
public class wrapper
{
private class innerclass
{
private innerclass()
{
}
innerclass(_cls1 _pcls1)
{
this();
}
}
public wrapper()
{
}
private void testinnerclass()
{
innerclass innerclass = new innerclass(null);
}
public static void main(string args[])
{
wrapper wrapper = new wrapper();
wrapper.testinnerclass();
}
// unreferenced inner classes:
/* anonymous class */
class _cls1
{
}
}
显然,wrapper$1类不会有任何实际的作用。那么为什么编译器一定要生成wrapper$1类,而不使用随便一个基本类型(例如byte)来作为占位符呢?我想,大概是因为使用wrapper$1可以使用更少的内存吧,因为一个空类是不会占用任何内存的(wrapper$1类没有任何成员变量,也就不会需要任何指向它的指针变量,事实上,即使删除wrapper$1.class文件,也不会影响程序运行,jvm将不会给出任何错误提示。),而任何一个可以有实际值的参数都会要求开辟一些内存来存放它。那么java的编译器不会做优化吗?问题是java编译器最终产生的只是class代码,在class代码的层次,无法向虚拟机表达这样的优化。而java虚拟机恐怕也不宜加入这种优化特性,所以sun就采用了现在的这种解决方法。需要说明的是,这只是我的猜测,由于我没有详细的阅读过jvm的全部文档,所以不能确定。
Java Asp PHP .Net XML C/C++ CGI VB Jsp J2ee J2se J2me EJB Servlet Tomcat Resin Struts Weblogic Eclipse ANT GUI JMS Web servise IDEA Webphere Hibernate Spring Jboss Applet Swing Socket Javamail Perl Ajax P2P 安全 模式 框架 测试 开源 游戏
Windows XP Windows 2000 Windows 2003 Windows Me Windows 9.x Linux UNIX 注册表 操作系统 服务器 应用服务器