读到
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),包括元信息和字节码。
在
OpenJDK的
Compiler项目中也有
描述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是一组标识位,其中包含了可访问性修饰符的记录。