JAVA private私有类的 默认构造函数 的生成过程

如果一个类没有定义任何构造函数,则编译器将生成一个缺省的构造函数,该构造函数的访问修改符和类的访问修改符相同,例如: 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就采用了现在的这种解决方法。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊flink的MemoryBackendCheckpointStorage

本文主要研究一下flink的MemoryBackendCheckpointStorage

9620
来自专栏pangguoming

Proguard使用最新,最全教程,亲自试验

最近公司有一个项目,是外包项目,由于对方也有技术人员,出于技术上的保密,需要对class文件进行二次处理,于是网上找了好久,只发现Proguard是用的最广泛而...

85230
来自专栏海天一树

小朋友学C++(14):两数交换

之前学C语言的时候,咱们直接在main函数中使用“异或”位运算符,很容易实现了两数交换。 本节课将在此基础上,把交换两个数的算法,封装到swap函数中。这样不管...

30670
来自专栏技术墨客

Spring和性——数据的类型转换

在字符串到实体转换一文中介绍了Spring核心框架中使用PropertyEditor将任何字符串转换为数字、实体的方法。除了字符串到实体,Spring还提供了更...

24530
来自专栏jeremy的技术点滴

koa框架源码解读

40580
来自专栏calmound

UVa Automatic Editing

uva的题真的很好,每个题都能长许多知识,A了后很开心,这道题我用了两天写,只一道题就学了四个函数,成长不少 Problem E: Automatic Edit...

35340
来自专栏WD学习记录

Python数据结构与算法笔记(1)

ADT(abstract data type)是由用户定义的数据类型,它制定了一组数据值的集合及可作用在这些数据值上的一组操作。ADT的定义与它的具体实现无关,...

30630
来自专栏步履前行

Java Validation Api

在我们应用程序的业务逻辑中,经常会碰到参数教研的情况,比如在Controller中,我们的参数是一个Entity的时候,经常要判断这个Entity的字段是否是...

33650
来自专栏函数式编程语言及工具

Scalaz(47)- scalaz-stream: 深入了解-Source

   scalaz-stream库的主要设计目标是实现函数式的I/O编程(functional I/O)。这样用户就能使用功能单一的基础I/O函数组合成为功能完...

29850
来自专栏海说

单元测试基本方法

依照类型划分,单元测试方法可以划分为两大类。一类是针对public方法进行测试,另一类是针对private方法进行测试。 public方法测试 public方法...

28900

扫码关注云+社区

领取腾讯云代金券