读到[url=http://zangxt.iteye.com]ZangXT[/url]写的[url=http://zangxt.iteye.com/blog/421510]默认构造方法并非总是public的[/url],顺便一提。
碰到语言问题先看规范。Java语言规范第三版8.8.9小节如是说:
[quote="Java Language Specification, 3rd Edition"][b][size=small]8.8.9 Default Constructor[/size][/b]
...
In an enum type [url=http://java.sun.com/docs/books/jls/third_edition/html/classes.html#301020](§8.9)[/url], the default constructor is implicitly private. Otherwise, if the class is declared public, then the default constructor is implicitly given the access modifier public [url=http://java.sun.com/docs/books/jls/third_edition/html/names.html#104285](§6.6)[/url]; if the class is declared protected, then the default constructor is implicitly given the access modifier protected [url=http://java.sun.com/docs/books/jls/third_edition/html/names.html#104285](§6.6)[/url]; if the class is declared private, then the default constructor is implicitly given the access modifier private [url=http://java.sun.com/docs/books/jls/third_edition/html/names.html#104285](§6.6)[/url]; otherwise, the default constructor has the default access implied by no access modifier.[/quote]
如ZangXT同学所说,默认构造器的可访问性是与类自身的可访问性相关的,并不总是public。
看完规范看实现。在Sun的javac编译器中,[url=http://hg.openjdk.java.net/jdk6/jdk6/langtools/file/9fff4785c52d/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java]com.sun.tools.javac.main.JavaCompiler[/url]类中的compile()方法里有:
同一个类中的compile2()方法里有:
也就是说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),包括元信息和字节码。
在[url=http://openjdk.java.net/]OpenJDK[/url]的[url=http://openjdk.java.net/groups/compiler]Compiler项目[/url]中也有[url=http://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html]描述javac的编译步骤的文档[/url]。
(不过OpenJDK站上不是所有文档都完全可信。在[url=http://openjdk.java.net/projects/compiler-grammar/]Compiler Grammar[/url]项目中的介绍:[quote]The parser that is currently in the javac compiler is a hand-written LALR parser.[/quote]就是错的。实际上现在的javac是基于LL(1)语法的递归下降式+运算符优先级解析,[url=http://rednaxelafx.iteye.com/blog/591157]之前一帖[/url]已经提过了。)
在上述流程中,默认构造器是在哪一步加进来的呢?下面用一段代码为例说明:
打开调试器,跟踪javac的编译过程,观察解析出来的语法树toString()之后的样子:
parseFiles后:
stopIfError对语法树没有修改。
enterTrees后:
嗯,默认构造器已经加进来了。那么关注一下enterTrees里的逻辑。
在[url=http://hg.openjdk.java.net/jdk6/jdk6/langtools/file/9fff4785c52d/src/share/classes/com/sun/tools/javac/comp/Enter.java]Enter[/url].complete()里,classEnter(trees, null);后都还没加上默认构造器。是在紧随其后的循环中clazz.complete();的调用加上了默认构造器。这是"enterTree"中的第二阶段,调用了MemberEnter.complete()。
[url=http://hg.openjdk.java.net/jdk6/jdk6/langtools/file/9fff4785c52d/src/share/classes/com/sun/tools/javac/comp/MemberEnter.java]com.sun.tools.javac.comp.MemberEnter[/url].complete()
其中在DefaultConstructor()方法中:
正是其中的 flags |= (c.flags() & AccessFlags) | GENERATEDCONSTR; 这句实现了默认构造器的可访问性与类的可访问性挂钩。c.flags()返回的long是一组标识位,其中包含了可访问性修饰符的记录。
碰到语言问题先看规范。Java语言规范第三版8.8.9小节如是说:
[quote="Java Language Specification, 3rd Edition"][b][size=small]8.8.9 Default Constructor[/size][/b]
...
In an enum type [url=http://java.sun.com/docs/books/jls/third_edition/html/classes.html#301020](§8.9)[/url], the default constructor is implicitly private. Otherwise, if the class is declared public, then the default constructor is implicitly given the access modifier public [url=http://java.sun.com/docs/books/jls/third_edition/html/names.html#104285](§6.6)[/url]; if the class is declared protected, then the default constructor is implicitly given the access modifier protected [url=http://java.sun.com/docs/books/jls/third_edition/html/names.html#104285](§6.6)[/url]; if the class is declared private, then the default constructor is implicitly given the access modifier private [url=http://java.sun.com/docs/books/jls/third_edition/html/names.html#104285](§6.6)[/url]; otherwise, the default constructor has the default access implied by no access modifier.[/quote]
如ZangXT同学所说,默认构造器的可访问性是与类自身的可访问性相关的,并不总是public。
看完规范看实现。在Sun的javac编译器中,[url=http://hg.openjdk.java.net/jdk6/jdk6/langtools/file/9fff4785c52d/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java]com.sun.tools.javac.main.JavaCompiler[/url]类中的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),包括元信息和字节码。
在[url=http://openjdk.java.net/]OpenJDK[/url]的[url=http://openjdk.java.net/groups/compiler]Compiler项目[/url]中也有[url=http://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html]描述javac的编译步骤的文档[/url]。
(不过OpenJDK站上不是所有文档都完全可信。在[url=http://openjdk.java.net/projects/compiler-grammar/]Compiler Grammar[/url]项目中的介绍:[quote]The parser that is currently in the javac compiler is a hand-written LALR parser.[/quote]就是错的。实际上现在的javac是基于LL(1)语法的递归下降式+运算符优先级解析,[url=http://rednaxelafx.iteye.com/blog/591157]之前一帖[/url]已经提过了。)
在上述流程中,默认构造器是在哪一步加进来的呢?下面用一段代码为例说明:
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里的逻辑。
在[url=http://hg.openjdk.java.net/jdk6/jdk6/langtools/file/9fff4785c52d/src/share/classes/com/sun/tools/javac/comp/Enter.java]Enter[/url].complete()里,classEnter(trees, null);后都还没加上默认构造器。是在紧随其后的循环中clazz.complete();的调用加上了默认构造器。这是"enterTree"中的第二阶段,调用了MemberEnter.complete()。
[url=http://hg.openjdk.java.net/jdk6/jdk6/langtools/file/9fff4785c52d/src/share/classes/com/sun/tools/javac/comp/MemberEnter.java]com.sun.tools.javac.comp.MemberEnter[/url].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是一组标识位,其中包含了可访问性修饰符的记录。