- 浏览: 3053035 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
读到ZangXT写的默认构造方法并非总是public的,顺便一提。
碰到语言问题先看规范。Java语言规范第三版8.8.9小节如是说:
如ZangXT同学所说,默认构造器的可访问性是与类自身的可访问性相关的,并不总是public。
看完规范看实现。在Sun的javac编译器中,com.sun.tools.javac.main.JavaCompiler类中的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),包括元信息和字节码。
在OpenJDK的Compiler项目中也有描述javac的编译步骤的文档。
(不过OpenJDK站上不是所有文档都完全可信。在Compiler Grammar项目中的介绍:
在上述流程中,默认构造器是在哪一步加进来的呢?下面用一段代码为例说明:
打开调试器,跟踪javac的编译过程,观察解析出来的语法树toString()之后的样子:
parseFiles后:
stopIfError对语法树没有修改。
enterTrees后:
嗯,默认构造器已经加进来了。那么关注一下enterTrees里的逻辑。
在Enter.complete()里,classEnter(trees, null);后都还没加上默认构造器。是在紧随其后的循环中clazz.complete();的调用加上了默认构造器。这是"enterTree"中的第二阶段,调用了MemberEnter.complete()。
com.sun.tools.javac.comp.MemberEnter.complete()
其中在DefaultConstructor()方法中:
正是其中的 flags |= (c.flags() & AccessFlags) | GENERATEDCONSTR; 这句实现了默认构造器的可访问性与类的可访问性挂钩。c.flags()返回的long是一组标识位,其中包含了可访问性修饰符的记录。
我这里是写得很简略……文中提到的官方文档对javac编译器的编译步骤描述的详细些,可以先阅读那个作为背景。
《深入Java虚拟机》第二版的第7章确实提到了构造器而没有提到可访问性的问题。本来这就是Java语言中的定义而不是JVM的定义,在这么一本书里没做全面的描述也是可以理解的。上面我已经引用了Java语言规范中相应的规定及其链接,读读规范会有更清晰的理解的。
我这篇东西只是作为ZangXT同学的那帖的补充,将规范中的定义与编译器中实现该特性的地方指出来而已。具体有什么不明白的地方我们也可以展开来讨论一下 ^_^
碰到语言问题先看规范。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.
...
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是一组标识位,其中包含了可访问性修饰符的记录。
评论
4 楼
ZangXT
2010-02-23
的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.
cpp太麻烦,啥时候生成,啥时候不生成……
java起码编译层保持个一致性
mxswl 写道
受益了.
这篇文章讲述了在编译阶段javac何时帮你拼凑constructor.
以前看过的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.
这篇文章讲述了在编译阶段javac何时帮你拼凑constructor.
以前看过的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.
cpp太麻烦,啥时候生成,啥时候不生成……
java起码编译层保持个一致性
3 楼
mxswl
2010-02-23
受益了.
这篇文章讲述了在编译阶段javac何时帮你拼凑constructor.
以前看过的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.
这篇文章讲述了在编译阶段javac何时帮你拼凑constructor.
以前看过的一本书,好像叫深入cpp对象模型,则花了很多篇幅讲述了产生默认构造函数的意义.
2 楼
RednaxelaFX
2010-02-23
zhxing 写道
说实话,没怎么看明白你。。可能功力不够深。。
最近也在看深入Java 虚拟机,大概意思是,Java 编译器为它编译的每一个类至少生成一个实例初始化方法(也就是构造方法),如果没有显示声明的任何构造方法的话,会默认生成一个无参数的构造方法,如果一个类没有显示调用另外一个构造方法的话,会自动生成一个超类的无参数的构造方法的调用。
在P168 页上有说明。但未说明它的访问权限。
最近也在看深入Java 虚拟机,大概意思是,Java 编译器为它编译的每一个类至少生成一个实例初始化方法(也就是构造方法),如果没有显示声明的任何构造方法的话,会默认生成一个无参数的构造方法,如果一个类没有显示调用另外一个构造方法的话,会自动生成一个超类的无参数的构造方法的调用。
在P168 页上有说明。但未说明它的访问权限。
我这里是写得很简略……文中提到的官方文档对javac编译器的编译步骤描述的详细些,可以先阅读那个作为背景。
《深入Java虚拟机》第二版的第7章确实提到了构造器而没有提到可访问性的问题。本来这就是Java语言中的定义而不是JVM的定义,在这么一本书里没做全面的描述也是可以理解的。上面我已经引用了Java语言规范中相应的规定及其链接,读读规范会有更清晰的理解的。
我这篇东西只是作为ZangXT同学的那帖的补充,将规范中的定义与编译器中实现该特性的地方指出来而已。具体有什么不明白的地方我们也可以展开来讨论一下 ^_^
1 楼
zhxing
2010-02-23
说实话,没怎么看明白你。。可能功力不够深。。
最近也在看深入Java 虚拟机,大概意思是,Java 编译器为它编译的每一个类至少生成一个实例初始化方法(也就是构造方法),如果没有显示声明的任何构造方法的话,会默认生成一个无参数的构造方法,如果一个类没有显示调用另外一个构造方法的话,会自动生成一个超类的无参数的构造方法的调用。
在P168 页上有说明。但未说明它的访问权限。
最近也在看深入Java 虚拟机,大概意思是,Java 编译器为它编译的每一个类至少生成一个实例初始化方法(也就是构造方法),如果没有显示声明的任何构造方法的话,会默认生成一个无参数的构造方法,如果一个类没有显示调用另外一个构造方法的话,会自动生成一个超类的无参数的构造方法的调用。
在P168 页上有说明。但未说明它的访问权限。
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
Java 8与静态工具类
2014-03-19 08:43 16294以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10465先看看下面这个代码例子, interface IFoo { ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22409(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对C语义的for循环的基本代码生成模式
2013-10-19 23:12 21886之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ... -
Nashorn各种笔记
2013-07-15 17:03 0http://bits.netbeans.org/netbea ... -
《深入理解Java虚拟机(第二版)》书评
2013-07-08 19:19 0值得推荐的中文Java虚拟机入门书 感谢作者赠与的样书,以下 ... -
豆列:从表到里学习JVM实现
2013-06-13 14:13 48398刚写了个学习JVM用的豆列跟大家分享。 豆列地址:http: ...
相关推荐
- **Java开发工具包(JDK)**:JDK是完整的Java开发工具包,包含了JRE、编译器(javac)、文档生成工具(Javadoc)、调试器(JDB)等用于开发Java应用程序的工具。简而言之,JDK包含了JRE的所有内容加上开发工具。 #### 3. ...
- **JDK (Java Development Kit)**:是在 JRE 基础上增加了开发工具的一整套开发环境,例如 Java 编译器(javac)、文档生成工具(javadoc)、调试器(jdb)等。简而言之,JDK 包含了 JRE 的所有内容再加上开发工具...
该程序可以正常编译和运行,输出`In test`两次(每次创建新实例时调用构造器),并打印出`this`引用(显示对象的内存地址)。 14. **集合初始化**: - 正确的集合初始化方式是使用`new String[]{"a", "b", "c"}`,...
特别地,Java会将未指定类型的浮点数默认为double类型,若想表示为float,需要在数字后加上f或F。另外,两个char类型的变量相加实际上是ASCII码的加法。对于整型变量,如果没有初始化,Java会自动赋予默认值0。 在...
- **方法引用和构造器引用**:与Lambda表达式配合,可以更直观地引用已有方法或构造器。 - **Stream API**:处理集合数据的新方式,支持链式操作,使得数据处理更高效。 - **默认方法**:接口中可以定义带实现的...
2. 运行jar文件时,需要在`java`命令后加上`-cp`或`-classpath`参数来指定类路径,以便找到包含class文件的目录或jar。 3. 在Java中,合法的标识符可以包含字母、数字和美元符号 `$`,但不能以数字开头或包含特殊...
- 创建 `String` 对象时,可以直接使用字符串文字或使用 `new String()` 构造器。但是,`new String(20)` 创建的是一个长度为20的字符数组,而不是一个真正的字符串。 #### 异常处理 - 在Java中,`catch` 块可以...
`javac`编译器会将所有`static`变量和`static`构造器代码按顺序合并。 - 如果这个类具有父类,JVM会先执行父类的`<clinit>`方法。 - JVM会保证`<clinit>`方法的加载是同步的,确保类只被加载一次。 #### 对象的...
- **JDK(Java Development Kit)**: Java开发工具包,包含了开发Java应用程序所需要的各种工具,例如编译工具(如javac)、解释工具(如java)、文档制作工具(如javadoc)、打包工具(如jar)等。 - **JRE(Java ...
4. **接口的默认方法**: 接口现在可以包含默认方法,这些方法具有默认实现,从而降低了接口升级的复杂性。 5. **重复注解**: 允许在同一位置使用多个相同的注解。 6. **类型推测**: 加强了类型推测机制,使得某些...
- PATH变量的设置使得用户可以在任意目录下使用Java编译器(javac)和Java解释器(java)。 #### 二、基本数据类型、变量和运算符 - **数据类型** - Java中每种数据类型都有明确的内存大小,如整型、浮点型、...
- **JDK(Java Development Kit)**: 包含了JRE,同时还包含了一系列开发工具,如编译器(javac)、调试器(jdb)等。JDK是开发Java应用程序所必需的。 - **区别**: JRE主要用于运行Java程序,而JDK主要用于开发Java...
局部变量需要在使用前进行初始化,而类变量(静态变量)和实例变量默认会被初始化为默认值。 10. **类型转换**: - 强制类型转换可以将一个表达式的类型转换为另一个类型,但需要注意可能发生的精度损失或溢出问题...
JVM在运行时通过类加载器将.class文件加载到内存中,并解析和执行其中的指令。这种机制确保了不同平台上运行的Java程序具有相同的执行逻辑,从而实现了真正的跨平台。 ### Java环境变量的配置 #### 一、JAVA_HOME ...
- `default`:默认权限,仅允许同一包内的其他类访问。 - `private`:私有权限,只有类内部可访问。 - **构造方法**: - 构造方法用于初始化对象。 - **数据和方法的隐藏——封装**: - 封装是指将数据和方法...