该篇文章涉及到了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 kouznetsov.
// 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的全部文档,所以不能确定。
闽公网安备 35060202000074号