`
RednaxelaFX
  • 浏览: 3039039 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

默认构造器在javac里是什么时候加上去的?

    博客分类:
  • Java
阅读更多
读到ZangXT写的默认构造方法并非总是public的,顺便一提。

碰到语言问题先看规范。Java语言规范第三版8.8.9小节如是说:
Java Language Specification, 3rd Edition 写道
8.8.9 Default Constructor

...

In an enum type (§8.9), the default constructor is implicitly private. Otherwise, if the class is declared public, then the default constructor is implicitly given the access modifier public (§6.6); if the class is declared protected, then the default constructor is implicitly given the access modifier protected (§6.6); if the class is declared private, then the default constructor is implicitly given the access modifier private (§6.6); otherwise, the default constructor has the default access implied by no access modifier.

如ZangXT同学所说,默认构造器的可访问性是与类自身的可访问性相关的,并不总是public。

看完规范看实现。在Sun的javac编译器中,com.sun.tools.javac.main.JavaCompiler类中的compile()方法里有:
delegateCompiler = processAnnotations(enterTrees(stopIfError(parseFiles(sourceFileObjects))),
                                      classnames);

delegateCompiler.compile2();

同一个类中的compile2()方法里有:
while (todo.nonEmpty())
    generate(desugar(flow(attribute(todo.next()))));

也就是说javac编译Java源码可以分为下述几步:
1、解析(parseFiles),如果有语法错误则停止(stopIfError);
2、将类型的信息补全,检查是否存在循环依赖等问题,没有问题则将类型信息输入到符号表里(enterTrees);
3、处理注解(processAnnotations),如果这步生成了新的类型则对新类型从第1步开始递归处理;
4、为语法树标注属性(attribute);
5、对程序做流相关分析(flow),用于检查确定性赋值等流相关语义分析;
6、解语法糖(desugar):将泛型类型转换为非泛型的;将for-each等语法糖转换为基本语法结构,包括Java 7新增的switch里的String支持也在这步;将内部类和嵌套类转换后当作顶层类处理;
7、生成Class文件(generate),包括元信息和字节码。

OpenJDKCompiler项目中也有描述javac的编译步骤的文档
(不过OpenJDK站上不是所有文档都完全可信。在Compiler Grammar项目中的介绍:
引用
The parser that is currently in the javac compiler is a hand-written LALR parser.
就是错的。实际上现在的javac是基于LL(1)语法的递归下降式+运算符优先级解析,之前一帖已经提过了。)

在上述流程中,默认构造器是在哪一步加进来的呢?下面用一段代码为例说明:
package test.debug.javac;

public class TestDefaultConstructorVisibility {
    public class TestDefaultConstructorVisibilityPublicInnerClass {
    }

    class TestDefaultConstructorVisibilityDefaultInnerClass {
    }

    protected class TestDefaultConstructorVisibilityProtectedInnerClass {
    }

    private class TestDefaultConstructorVisibilityPrivateInnerClass {
    }
}

class TestDefaultConstructorVisibilityPackageVisibilityCase {
}


打开调试器,跟踪javac的编译过程,观察解析出来的语法树toString()之后的样子:
parseFiles后:
package test.debug.javac;

public class TestDefaultConstructorVisibility {
    
    public class TestDefaultConstructorVisibilityPublicInnerClass {
    }
    
    class TestDefaultConstructorVisibilityDefaultInnerClass {
    }
    
    protected class TestDefaultConstructorVisibilityProtectedInnerClass {
    }
    
    private class TestDefaultConstructorVisibilityPrivateInnerClass {
    }
}
class TestDefaultConstructorVisibilityPackageVisibilityCase {
}


stopIfError对语法树没有修改。

enterTrees后:
package test.debug.javac;

public class TestDefaultConstructorVisibility {
    
    public TestDefaultConstructorVisibility() {
        super();
    }
    
    public class TestDefaultConstructorVisibilityPublicInnerClass {
        
        public TestDefaultConstructorVisibilityPublicInnerClass() {
            super();
        }
    }
    
    class TestDefaultConstructorVisibilityDefaultInnerClass {
        
        TestDefaultConstructorVisibilityDefaultInnerClass() {
            super();
        }
    }
    
    protected class TestDefaultConstructorVisibilityProtectedInnerClass {
        
        protected TestDefaultConstructorVisibilityProtectedInnerClass() {
            super();
        }
    }
    
    private class TestDefaultConstructorVisibilityPrivateInnerClass {
        
        private TestDefaultConstructorVisibilityPrivateInnerClass() {
            super();
        }
    }
}
class TestDefaultConstructorVisibilityPackageVisibilityCase {
    
    TestDefaultConstructorVisibilityPackageVisibilityCase() {
        super();
    }
}

嗯,默认构造器已经加进来了。那么关注一下enterTrees里的逻辑。

Enter.complete()里,classEnter(trees, null);后都还没加上默认构造器。是在紧随其后的循环中clazz.complete();的调用加上了默认构造器。这是"enterTree"中的第二阶段,调用了MemberEnter.complete()。
com.sun.tools.javac.comp.MemberEnter.complete()
/* ********************************************************************
* Source completer
*********************************************************************/

/** Complete entering a class.
 *  @param sym         The symbol of the class to be completed.
 */
public void complete(Symbol sym) throws CompletionFailure {
    // Suppress some (recursive) MemberEnter invocations
    // ...

    ClassSymbol c = (ClassSymbol)sym;
    ClassType ct = (ClassType)c.type;
    Env<AttrContext> env = enter.typeEnvs.get(c);
    JCClassDecl tree = (JCClassDecl)env.tree;
    boolean wasFirst = isFirst;
    isFirst = false;

    JavaFileObject prev = log.useSource(env.toplevel.sourcefile);
    try {
        // ...

        // Add default constructor if needed.
        if ((c.flags() & INTERFACE) == 0 &&
            !TreeInfo.hasConstructors(tree.defs)) {
            List<Type> argtypes = List.nil();
            List<Type> typarams = List.nil();
            List<Type> thrown = List.nil();
            long ctorFlags = 0;
            boolean based = false;
            if (c.name.len == 0) {
                JCNewClass nc = (JCNewClass)env.next.tree;
                if (nc.constructor != null) {
                    Type superConstrType = types.memberType(c.type,
                                                            nc.constructor);
                    argtypes = superConstrType.getParameterTypes();
                    typarams = superConstrType.getTypeArguments();
                    ctorFlags = nc.constructor.flags() & VARARGS;
                    if (nc.encl != null) {
                        argtypes = argtypes.prepend(nc.encl.type);
                        based = true;
                    }
                    thrown = superConstrType.getThrownTypes();
                }
            }
            JCTree constrDef = DefaultConstructor(make.at(tree.pos), c,
                                                typarams, argtypes, thrown,
                                                ctorFlags, based);
            tree.defs = tree.defs.prepend(constrDef);
        }

        // ...
    } catch (CompletionFailure ex) {
        chk.completionError(tree.pos(), ex);
    } finally {
        log.useSource(prev);
    }

    // ...
}

其中在DefaultConstructor()方法中:
/** Generate default constructor for given class. For classes different
 *  from java.lang.Object, this is:
 *
 *    c(argtype_0 x_0, ..., argtype_n x_n) throws thrown {
 *      super(x_0, ..., x_n)
 *    }
 *
 *  or, if based == true:
 *
 *    c(argtype_0 x_0, ..., argtype_n x_n) throws thrown {
 *      x_0.super(x_1, ..., x_n)
 *    }
 *
 *  @param make     The tree factory.
 *  @param c        The class owning the default constructor.
 *  @param argtypes The parameter types of the constructor.
 *  @param thrown   The thrown exceptions of the constructor.
 *  @param based    Is first parameter a this$n?
 */
JCTree DefaultConstructor(TreeMaker make,
                        ClassSymbol c,
                        List<Type> typarams,
                        List<Type> argtypes,
                        List<Type> thrown,
                        long flags,
                        boolean based) {
    List<JCVariableDecl> params = make.Params(argtypes, syms.noSymbol);
    List<JCStatement> stats = List.nil();
    if (c.type != syms.objectType)
        stats = stats.prepend(SuperCall(make, typarams, params, based));
    if ((c.flags() & ENUM) != 0 &&
        (types.supertype(c.type).tsym == syms.enumSym ||
         target.compilerBootstrap(c))) {
        // constructors of true enums are private
        flags = (flags & ~AccessFlags) | PRIVATE | GENERATEDCONSTR;
    } else
        flags |= (c.flags() & AccessFlags) | GENERATEDCONSTR;
    if (c.name.len == 0) flags |= ANONCONSTR;
    JCTree result = make.MethodDef(
        make.Modifiers(flags),
        names.init,
        null,
        make.TypeParams(typarams),
        params,
        make.Types(thrown),
        make.Block(0, stats),
        null);
    return result;
}

正是其中的 flags |= (c.flags() & AccessFlags) | GENERATEDCONSTR; 这句实现了默认构造器的可访问性与类的可访问性挂钩。c.flags()返回的long是一组标识位,其中包含了可访问性修饰符的记录。
11
1
分享到:
评论
4 楼 ZangXT 2010-02-23  
的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.
mxswl 写道
受益了.
这篇文章讲述了在编译阶段javac何时帮你拼凑constructor.
以前看过的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.

cpp太麻烦,啥时候生成,啥时候不生成……
java起码编译层保持个一致性
3 楼 mxswl 2010-02-23  
受益了.
这篇文章讲述了在编译阶段javac何时帮你拼凑constructor.
以前看过的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.
2 楼 RednaxelaFX 2010-02-23  
zhxing 写道
说实话,没怎么看明白你。。可能功力不够深。。
最近也在看深入Java 虚拟机,大概意思是,Java 编译器为它编译的每一个类至少生成一个实例初始化方法(也就是构造方法),如果没有显示声明的任何构造方法的话,会默认生成一个无参数的构造方法,如果一个类没有显示调用另外一个构造方法的话,会自动生成一个超类的无参数的构造方法的调用。
在P168 页上有说明。但未说明它的访问权限。

我这里是写得很简略……文中提到的官方文档对javac编译器的编译步骤描述的详细些,可以先阅读那个作为背景。

《深入Java虚拟机》第二版的第7章确实提到了构造器而没有提到可访问性的问题。本来这就是Java语言中的定义而不是JVM的定义,在这么一本书里没做全面的描述也是可以理解的。上面我已经引用了Java语言规范中相应的规定及其链接,读读规范会有更清晰的理解的。

我这篇东西只是作为ZangXT同学的那帖的补充,将规范中的定义与编译器中实现该特性的地方指出来而已。具体有什么不明白的地方我们也可以展开来讨论一下 ^_^
1 楼 zhxing 2010-02-23  
说实话,没怎么看明白你。。可能功力不够深。。
最近也在看深入Java 虚拟机,大概意思是,Java 编译器为它编译的每一个类至少生成一个实例初始化方法(也就是构造方法),如果没有显示声明的任何构造方法的话,会默认生成一个无参数的构造方法,如果一个类没有显示调用另外一个构造方法的话,会自动生成一个超类的无参数的构造方法的调用。
在P168 页上有说明。但未说明它的访问权限。

相关推荐

    2016年Java面试题及回答.doc

    - **Java开发工具包(JDK)**:JDK是完整的Java开发工具包,包含了JRE、编译器(javac)、文档生成工具(Javadoc)、调试器(JDB)等用于开发Java应用程序的工具。简而言之,JDK包含了JRE的所有内容加上开发工具。 #### 3. ...

    JAVA常见面试题300道

    - **JDK (Java Development Kit)**:是在 JRE 基础上增加了开发工具的一整套开发环境,例如 Java 编译器(javac)、文档生成工具(javadoc)、调试器(jdb)等。简而言之,JDK 包含了 JRE 的所有内容再加上开发工具...

    Corejava 核心知识点测试题及答案

    该程序可以正常编译和运行,输出`In test`两次(每次创建新实例时调用构造器),并打印出`this`引用(显示对象的内存地址)。 14. **集合初始化**: - 正确的集合初始化方式是使用`new String[]{"a", "b", "c"}`,...

    java学习笔记 继承 重载 覆盖

    特别地,Java会将未指定类型的浮点数默认为double类型,若想表示为float,需要在数字后加上f或F。另外,两个char类型的变量相加实际上是ASCII码的加法。对于整型变量,如果没有初始化,Java会自动赋予默认值0。 在...

    1、JDK1.8安装及环境配置-解压缩版

    - **方法引用和构造器引用**:与Lambda表达式配合,可以更直观地引用已有方法或构造器。 - **Stream API**:处理集合数据的新方式,支持链式操作,使得数据处理更高效。 - **默认方法**:接口中可以定义带实现的...

    Java选择题分析 (4).pdf

    2. 运行jar文件时,需要在`java`命令后加上`-cp`或`-classpath`参数来指定类路径,以便找到包含class文件的目录或jar。 3. 在Java中,合法的标识符可以包含字母、数字和美元符号 `$`,但不能以数字开头或包含特殊...

    (完整word版)Java程序设计B卷.docx

    - 创建 `String` 对象时,可以直接使用字符串文字或使用 `new String()` 构造器。但是,`new String(20)` 创建的是一个长度为20的字符数组,而不是一个真正的字符串。 #### 异常处理 - 在Java中,`catch` 块可以...

    jvm笔记记录.docx

    `javac`编译器会将所有`static`变量和`static`构造器代码按顺序合并。 - 如果这个类具有父类,JVM会先执行父类的`&lt;clinit&gt;`方法。 - JVM会保证`&lt;clinit&gt;`方法的加载是同步的,确保类只被加载一次。 #### 对象的...

    2021-2022计算机二级等级考试试题及答案No.12181.docx

    - **JDK(Java Development Kit)**: Java开发工具包,包含了开发Java应用程序所需要的各种工具,例如编译工具(如javac)、解释工具(如java)、文档制作工具(如javadoc)、打包工具(如jar)等。 - **JRE(Java ...

    2021秋招】Java 面试知识点【精华背诵版

    4. **接口的默认方法**: 接口现在可以包含默认方法,这些方法具有默认实现,从而降低了接口升级的复杂性。 5. **重复注解**: 允许在同一位置使用多个相同的注解。 6. **类型推测**: 加强了类型推测机制,使得某些...

    Java基础(韩顺平版)笔记详

    - PATH变量的设置使得用户可以在任意目录下使用Java编译器(javac)和Java解释器(java)。 #### 二、基本数据类型、变量和运算符 - **数据类型** - Java中每种数据类型都有明确的内存大小,如整型、浮点型、...

    Java面试宝典

    - **JDK(Java Development Kit)**: 包含了JRE,同时还包含了一系列开发工具,如编译器(javac)、调试器(jdb)等。JDK是开发Java应用程序所必需的。 - **区别**: JRE主要用于运行Java程序,而JDK主要用于开发Java...

    完整word版,Java期末复习题华广概念填空题(含答案).docx

    局部变量需要在使用前进行初始化,而类变量(静态变量)和实例变量默认会被初始化为默认值。 10. **类型转换**: - 强制类型转换可以将一个表达式的类型转换为另一个类型,但需要注意可能发生的精度损失或溢出问题...

    Java course

    JVM在运行时通过类加载器将.class文件加载到内存中,并解析和执行其中的指令。这种机制确保了不同平台上运行的Java程序具有相同的执行逻辑,从而实现了真正的跨平台。 ### Java环境变量的配置 #### 一、JAVA_HOME ...

    CoreJavaNoteBook

    - `default`:默认权限,仅允许同一包内的其他类访问。 - `private`:私有权限,只有类内部可访问。 - **构造方法**: - 构造方法用于初始化对象。 - **数据和方法的隐藏——封装**: - 封装是指将数据和方法...

Global site tag (gtag.js) - Google Analytics