- 浏览: 3047967 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (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分享的概要
如题。这个问题的答案要看你心中的“静态”指代了什么。
看到最近在论坛的一帖:
想起以前在JavaEye问答频道见过的类似问题,看来这“静态”有一定迷惑性的。
在Java中,“static”可以有多个意思,对方法而言,至少包括下面两点:
1、Java语言中的“static”关键字用于修饰方法时,表示“静态方法”,与“实例方法”相对。
2、在讨论方法的具体调用目标时,一个方法调用到底能否在运行前就确定一个固定的目标,是则可以进行“静态绑定”(static binding),否则需要做“运行时绑定”(runtime binding)。这与“static”关键字不是一回事。
===========================================================================
先看看第一点,Java语言中的“static“关键字修饰的方法。
根据Java语言规范第三版,静态方法的规定为:
注意到规范中关于“静态方法”(红色部分)与“实例方法”(蓝色部分)的定义。两者的关键差异在于:“静态方法”的调用总是不指定某个对象实例为“接收者”,而“实例方法”则总是要以某个对象实例为“接收者”(receiver)。
用Java的语法来演示。
调用实例方法,从调用者的一方看,“接收者”就是“点”之前的那个变量所引用的对象:
如果一个被被调用者与调用者在同一个类中,那么receiver可以省略不写,由编译器判断出“接收者”是this。
从被调用的一方看,“接收者”就是在方法中可以使用的伪变量“this”:
“this”并没有出现在参数列表中,但它实际上做作为实例方法调用的一个隐式参数传入的。
调用静态方法则不需要、无法指定也无法使用receiver。Java语言中的类不是对象,所以通过ClassName.aStaticMethod(args)的方式去调用一个静态方法时,“点”前面的并不是receiver。如果“点”前面的是一个指向某对象实例的变量而“点”后面指定的是一个静态方法,则实际上那个变量并没有被作为receiver使用,只是个调用变量的类型上声明的静态方法的语法糖而已。也就是说:
实际上等效于:
关于“this”的规定,Java语言规范第三版如是说:
很明显,在构造器中是可以访问“this”的;实例初始化器与实例变量初始化器在编译时会与构造器一起被收集到<init>()方法中,它们也都可以访问“this”。所以从Java语言的“static”关键字的角度看,实例构造器不是“静态方法”。
-------------------------------------------
Java语言通常是编译为class文件后由Java虚拟机来运行的。在Java虚拟机规范第二版中,有这样的描述:
3.6.1小节的最后一段提到了“类方法”(“静态方法”)与“实例方法”在概念中的JVM上的区别:在调用类方法时,所有参数按顺序存放于被调用方法的局部变量区中的连续区域,从局部变量0开始;在调用实例方法时,局部变量0用于存放传入的该方法所属的对象实例(Java语言中的“this”),所有参数从局部变量1开始存放在局部变量区的连续区域中。
从效果上看,这就等于在调用实例方法时总是把“this”作为第一个参数传入被调用方法。
在关于方法描述符的部分:
这里提到一个方法无论是类方法还是实例方法,其方法描述符都是一样的。“this”作为调用实例方法的一个隐式参数,不会反映在方法描述符中。
===========================================================================
接下来看第二点,关于调用方法时选择具体的目标的“static”。
Java语言中,虚方法可以通过覆写(override)的方式来实现子类型多态(subtype polymorphism)。Java语言支持三种多态,除了子类型多态外还有通过方法重载支持的ad-hoc多态(ad-hoc polymorphism)与通过泛型支持的参数化多态(parametric polymorphism)。在面向对象编程的语境里“多态”一般指子类型多态,下面提到“多态”一词也特定指子类型多态。
Java语言中非虚方法可以通过“静态绑定”(static binding)或者叫“早绑定”(early binding)来选择实际的调用目标——因为无法覆写,无法产生多态的效果,于是可能的调用目标总是固定的一个。虚方法则一般需要等到运行时根据“接收者”的具体类型来选择到底要调用哪个版本的方法,这个过程称为“运行时绑定”(runtime binding)或者叫“迟绑定”(late-binding)。
不过Java的虚方法的迟绑定具体如何去选择目标是写死在语言规范与JVM的实现中的,用户无法干涉选择的过程。这使得Java提供的迟绑定缺乏自由度。在Java 7开始提供invokedynamic支持后,用户可以自行编写程序来控制迟绑定的过程,开始对选择调用目标拥有完整的控制权。
Java语言中,哪些方法是虚方法呢?静态方法全部都是非虚的,而实例方法则看情况。
Java语言规范第三版说明了哪些实例方法不是虚方法:
行为如同“final”的方法都无法覆写,也就无法进行子类型多态;声明为final或private的方法都被属于这类。所以除了静态方法之外,声明为final或者private的实例方法也是非虚方法。其它实例方法都是虚方法。
Java语言规范接着提到:
这里提到“final方法”可以在运行时得到内联。其实所有非虚方法在运行时都可以安全的被内联。
一个保守的JVM可以如上述说明一样在运行时对非虚方法的调用进行内联优化;而一个激进优化的JVM则可以更进一步,将源码中声明为虚方法、但在运行时的某个时间点可以证明(例如通过类层次分析(CHA))该方法只有一个可能的调用目标时,仍然可以将调用目标内联到调用者中。现在在桌面与服务器上的主流高性能JVM,如Oracle (Sun) HotSpot、IBM J9、Oracle (BEA) JRockit等,都会做这样的激进优化。因此在开发桌面与服务器端Java程序时没有必要为了提到性能而特意将方法声明为final的。
关于HotSpot VM对final的处理,可以参考HotSpot Internals wiki的Virtual Calls一篇:
Anders Hejlsberg曾经在多个场合提到虚方法是无法内联的(例如这个Artima的访谈);确实CLR是无法内联任何虚方法调用,但那只是CLR的实现限制而已。这点上高性能JVM比CLR要先进(且复杂)许多。
来看看Oracle/Sun JDK里的HotSpot VM是如何做初步的是否能静态绑定的:
也就是,如果某个Java方法是final的或者不是虚方法的话,它就可以做静态绑定。
-------------------------------------------
Java虚拟机规范第二版中定义了四种不同的字节码指令来处理Java程序中不同种类的方法的调用。包括,
· invokestatic - 用于调用类(静态)方法
· invokespecial - 用于调用实例方法,特化于super方法调用、private方法调用与构造器调用
· invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法)
· invokeinterface - 用于调用接口方法
其中,invokestatic与invokespecial调用的目标必然是可以静态绑定的,因为它们都无法参与子类型多态;invokevirtual与invokeinterface的则一般需要做运行时绑定,JVM实现可以有选择的根据final或实际运行时类层次或类型反馈等信息试图进行静态绑定。
===========================================================================
那么Java中的实例构造器是不是“静态方法”呢?从Java语言规范中给出的“静态方法”的定义来看,答案是“否”——首先从Java语言规范对“方法”的定义来说,构造器根本不是“方法”;其次,实例构造器有一个隐式参数,“this”,在实例构造器中可以访问“this”,可以通过“this”访问到正在初始化的对象实例的所有实例成员。
Java语言规范中关于构造器的说明中提到:
实例构造器无法被隐藏或覆写,不参与多态,因而可以做静态绑定。从这个意义上可以认为实例构造器是“静态”的,但这种用法与Java语言定义的“静态方法”是两码事。
另外需要注意的是,Java语言中,实例构造器只能在new表达式(或别的构造器)中被调用,不能通过方法调用表达式来调用。new表达式作为一个整体保证了对象的创建与初始化是打包在一起进行的,不能分开进行;但实例构造器只负责对象初始化的部分,“创建对象”的部分是由new表达式本身保证的。
举个例子,下面的Java代码
被编译为class文件后,实例构造器与main()方法的内容分别为:
先从main()方法开始看。
第一条指令是new,用于创建出ConstructorDemo类型的一个空对象,执行过后指向该对象的引用被压到操作数栈上。
第二条指令是dup,将操作数栈顶的值复制一份压回到栈顶;其中dup出来的一份用于作为隐式参数传到实例构造器里去(对应后面的invokespecial),原本的一份用于保存到局部变量去(对应后面的astore_1)。
第三条指令是iconst_2,将常量2压到操作数栈上,作为ConstructorDemo实例构造器的第一个显式参数。
第四条指令是aload_0,将main()方法的参数args作为ConstructorDemo实例构造器的第二个显式参数。
第五条指令是invokespecial,调用ConstructorDemo实例构造器。再次留意,前面已经传了三个参数,分别是new出来的实例的引用、常量2与main()的参数args。该指令执行过后,操作数栈顶就只剩下dup前通过new得到的引用。
第6条指令是astore_1,将操作数栈顶的引用保存到局部变量1中。执行过后操作数栈空了。
最后一条指令是return,结束main()方法的执行并返回。
然后从ConstructorDemo的实例构造器来看。
第一条指令是aload_0,将第一个参数(不管是隐式还是显式参数)压到操作数栈上。从main()的调用序列可以看到第一个参数是刚new出来的对象实例的引用,对这个构造器来说也就是“this”。
第二条指令是invokespecial,调用Object的实例构造器。前一条指令的“this”就是这个调用的参数。执行过后操作数栈就空了。
第三条指令又是aload_0,再次将“this”压到操作数栈上。
第四条指令是iload_1,将第二个参数压到操作数栈上,也就是i。
第五条指令是putfield,将i赋值给this.value。执行过后操作数栈又空了。
最后一条指令是return,结束该实例构造器的执行并返回。
这个例子的注意点在于:
1、Java的实例构造器只负责初始化,不负责创建对象;Java虚拟机的字节码指令的设计也反映了这一点,有一个new指令专门用于创建对象实例,而调用实例构造器则使用invokespecial指令。
2、“this”是作为实例构造器的第一个实际参数传入的。
相信能区分“静态方法”与“静态绑定”中的“静态”之后,就不会再将Java中的实例构造器看作是“静态方法”了。
你可能需要把显示放大点。签名里的两个参数的类型分别是:
I
Ljava/lang/Object;
那个I就是int。
在JVM标准2中也有介绍。(实例的初始化方法也是实例的方法)
嗯这段在上面的原文里也引用过了,3.6.1的蓝字。
JTOC和TIB都是Jikes RVM/MRP才使用的叫法。别的JVM实现也不叫这个…
不好意思 我看错了 没 问题
学。
当然也可以指望别人。毕玄 @bluedavy 有向刘总编 @turingbook 建议引进这本书。如果有人翻译的话就可以坐等中文版了。但我仍然强烈推荐阅读第一手资料。
Oracle JRockit: The Definitive Guide
我英语不好,咋办?
Oracle JRockit: The Definitive Guide
看书不仔细 所以没被误导 晕.....
分析的对象不是“字节码”而是“类层次结构”;做了这个分析后,多态方法也照样可以激进内联。实际的高性能JVM中也是这么做的。
类层次结构不在字节码体现?
Class文件中字节码只用于表示代码逻辑,类层次结构是在字节码之外的元信息里记录的。例子在之前一帖的演示稿里有,有兴趣的话可以参考。
嗯,谢谢,确实不太记得了。
分析的对象不是“字节码”而是“类层次结构”;做了这个分析后,多态方法也照样可以激进内联。实际的高性能JVM中也是这么做的。
类层次结构不在字节码体现?
Class文件中字节码只用于表示代码逻辑,类层次结构是在字节码之外的元信息里记录的。例子在之前一帖的演示稿里有,有兴趣的话可以参考。
看到最近在论坛的一帖:
renpeng301 写道
如果不熟悉JVM指令,看到这些东西确实难以理解···很直观的看到Test默认为继承自Object这个JAVA中的超级父类,当new Test()的时候,调用Test的默认构造器,构造器其实就是一个特殊的静态的方法(这样说应该没错吧?)
想起以前在JavaEye问答频道见过的类似问题,看来这“静态”有一定迷惑性的。
在Java中,“static”可以有多个意思,对方法而言,至少包括下面两点:
1、Java语言中的“static”关键字用于修饰方法时,表示“静态方法”,与“实例方法”相对。
2、在讨论方法的具体调用目标时,一个方法调用到底能否在运行前就确定一个固定的目标,是则可以进行“静态绑定”(static binding),否则需要做“运行时绑定”(runtime binding)。这与“static”关键字不是一回事。
===========================================================================
先看看第一点,Java语言中的“static“关键字修饰的方法。
根据Java语言规范第三版,静态方法的规定为:
Java Language Specification, 3rd 写道
8.4.3.2 static Methods
A method that is declared static is called a class method. A class method is always invoked without reference to a particular object. An attempt to reference the current object using the keyword this or the keyword super or to reference the type parameters of any surrounding declaration in the body of a class method results in a compile-time error. It is a compile-time error for a static method to be declared abstract.
A method that is not declared static is called an instance method, and sometimes called a non-static method. An instance method is always invoked with respect to an object, which becomes the current object to which the keywords this and super refer during execution of the method body.
A method that is declared static is called a class method. A class method is always invoked without reference to a particular object. An attempt to reference the current object using the keyword this or the keyword super or to reference the type parameters of any surrounding declaration in the body of a class method results in a compile-time error. It is a compile-time error for a static method to be declared abstract.
A method that is not declared static is called an instance method, and sometimes called a non-static method. An instance method is always invoked with respect to an object, which becomes the current object to which the keywords this and super refer during execution of the method body.
注意到规范中关于“静态方法”(红色部分)与“实例方法”(蓝色部分)的定义。两者的关键差异在于:“静态方法”的调用总是不指定某个对象实例为“接收者”,而“实例方法”则总是要以某个对象实例为“接收者”(receiver)。
用Java的语法来演示。
调用实例方法,从调用者的一方看,“接收者”就是“点”之前的那个变量所引用的对象:
receiver.instanceMethod(args)
如果一个被被调用者与调用者在同一个类中,那么receiver可以省略不写,由编译器判断出“接收者”是this。
从被调用的一方看,“接收者”就是在方法中可以使用的伪变量“this”:
public void instanceMethod(Object... args) { // 注意“this” System.out.println(this); }
“this”并没有出现在参数列表中,但它实际上做作为实例方法调用的一个隐式参数传入的。
调用静态方法则不需要、无法指定也无法使用receiver。Java语言中的类不是对象,所以通过ClassName.aStaticMethod(args)的方式去调用一个静态方法时,“点”前面的并不是receiver。如果“点”前面的是一个指向某对象实例的变量而“点”后面指定的是一个静态方法,则实际上那个变量并没有被作为receiver使用,只是个调用变量的类型上声明的静态方法的语法糖而已。也就是说:
aVariable.aStaticMethod(args)
实际上等效于:
TheClass.aStaticMethod(args)
关于“this”的规定,Java语言规范第三版如是说:
Java Language Specification, 3rd 写道
15.8.3 this
The keyword this may be used only in the body of an instance method, instance initializer or constructor, or in the initializer of an instance variable of a class. If it appears anywhere else, a compile-time error occurs.
The keyword this may be used only in the body of an instance method, instance initializer or constructor, or in the initializer of an instance variable of a class. If it appears anywhere else, a compile-time error occurs.
很明显,在构造器中是可以访问“this”的;实例初始化器与实例变量初始化器在编译时会与构造器一起被收集到<init>()方法中,它们也都可以访问“this”。所以从Java语言的“static”关键字的角度看,实例构造器不是“静态方法”。
-------------------------------------------
Java语言通常是编译为class文件后由Java虚拟机来运行的。在Java虚拟机规范第二版中,有这样的描述:
Java virtual machine specification, 2nd 写道
3.6.1 Local Variables
Each frame (§3.6) contains an array of variables known as its local variables. The length of the local variable array of a frame is determined at compile time and supplied in the binary representation of a class or interface along with the code for the method associated with the frame (§4.7.3).
A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress. A pair of local variables can hold a value of type long or double.
Local variables are addressed by indexing. The index of the first local variable is zero. An integer is be considered to be an index into the local variable array if and only if that integer is between zero and one less than the size of the local variable array.
A value of type long or type double occupies two consecutive local variables. Such a value may only be addressed using the lesser index. For example, a value of type double stored in the local variable array at index n actually occupies the local variables with indices n and n +1; however, the local variable at index n +1 cannot be loaded from. It can be stored into. However, doing so invalidates the contents of local variable n.
The Java virtual machine does not require n to be even. In intuitive terms, values of types double and long need not be 64-bit aligned in the local variables array. Implementors are free to decide the appropriate way to represent such values using the two local variables reserved for the value.
The Java virtual machine uses local variables to pass parameters on method invocation. On class method invocation any parameters are passed in consecutive local variables starting from local variable 0. On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language). Any parameters are subsequently passed in consecutive local variables starting from local variable 1.
Each frame (§3.6) contains an array of variables known as its local variables. The length of the local variable array of a frame is determined at compile time and supplied in the binary representation of a class or interface along with the code for the method associated with the frame (§4.7.3).
A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress. A pair of local variables can hold a value of type long or double.
Local variables are addressed by indexing. The index of the first local variable is zero. An integer is be considered to be an index into the local variable array if and only if that integer is between zero and one less than the size of the local variable array.
A value of type long or type double occupies two consecutive local variables. Such a value may only be addressed using the lesser index. For example, a value of type double stored in the local variable array at index n actually occupies the local variables with indices n and n +1; however, the local variable at index n +1 cannot be loaded from. It can be stored into. However, doing so invalidates the contents of local variable n.
The Java virtual machine does not require n to be even. In intuitive terms, values of types double and long need not be 64-bit aligned in the local variables array. Implementors are free to decide the appropriate way to represent such values using the two local variables reserved for the value.
The Java virtual machine uses local variables to pass parameters on method invocation. On class method invocation any parameters are passed in consecutive local variables starting from local variable 0. On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language). Any parameters are subsequently passed in consecutive local variables starting from local variable 1.
3.6.1小节的最后一段提到了“类方法”(“静态方法”)与“实例方法”在概念中的JVM上的区别:在调用类方法时,所有参数按顺序存放于被调用方法的局部变量区中的连续区域,从局部变量0开始;在调用实例方法时,局部变量0用于存放传入的该方法所属的对象实例(Java语言中的“this”),所有参数从局部变量1开始存放在局部变量区的连续区域中。
从效果上看,这就等于在调用实例方法时总是把“this”作为第一个参数传入被调用方法。
在关于方法描述符的部分:
Java virtual machine specification, 2nd 写道
4.3.3 Method Descriptors
(... 省略)
For example, the method descriptor for the method
Object mymethod(int i, double d, Thread t)
is
(IDLjava/lang/Thread;)Ljava/lang/Object;
Note that internal forms of the fully qualified names of Thread and Object are used in the method descriptor.
The method descriptor for mymethod is the same whether mymethod is a class or an instance method. Although an instance method is passed this, a reference to the current class instance, in addition to its intended parameters, that fact is not reflected in the method descriptor. (A reference to this is not passed to a class method.) The reference to this is passed implicitly by the method invocation instructions of the Java virtual machine used to invoke instance methods.
(... 省略)
For example, the method descriptor for the method
Object mymethod(int i, double d, Thread t)
is
(IDLjava/lang/Thread;)Ljava/lang/Object;
Note that internal forms of the fully qualified names of Thread and Object are used in the method descriptor.
The method descriptor for mymethod is the same whether mymethod is a class or an instance method. Although an instance method is passed this, a reference to the current class instance, in addition to its intended parameters, that fact is not reflected in the method descriptor. (A reference to this is not passed to a class method.) The reference to this is passed implicitly by the method invocation instructions of the Java virtual machine used to invoke instance methods.
这里提到一个方法无论是类方法还是实例方法,其方法描述符都是一样的。“this”作为调用实例方法的一个隐式参数,不会反映在方法描述符中。
===========================================================================
接下来看第二点,关于调用方法时选择具体的目标的“static”。
Java语言中,虚方法可以通过覆写(override)的方式来实现子类型多态(subtype polymorphism)。Java语言支持三种多态,除了子类型多态外还有通过方法重载支持的ad-hoc多态(ad-hoc polymorphism)与通过泛型支持的参数化多态(parametric polymorphism)。在面向对象编程的语境里“多态”一般指子类型多态,下面提到“多态”一词也特定指子类型多态。
Java语言中非虚方法可以通过“静态绑定”(static binding)或者叫“早绑定”(early binding)来选择实际的调用目标——因为无法覆写,无法产生多态的效果,于是可能的调用目标总是固定的一个。虚方法则一般需要等到运行时根据“接收者”的具体类型来选择到底要调用哪个版本的方法,这个过程称为“运行时绑定”(runtime binding)或者叫“迟绑定”(late-binding)。
不过Java的虚方法的迟绑定具体如何去选择目标是写死在语言规范与JVM的实现中的,用户无法干涉选择的过程。这使得Java提供的迟绑定缺乏自由度。在Java 7开始提供invokedynamic支持后,用户可以自行编写程序来控制迟绑定的过程,开始对选择调用目标拥有完整的控制权。
Java语言中,哪些方法是虚方法呢?静态方法全部都是非虚的,而实例方法则看情况。
Java语言规范第三版说明了哪些实例方法不是虚方法:
Java Language Specification, 3rd 写道
8.4.3.3 final Methods
A method can be declared final to prevent subclasses from overriding or hiding it. It is a compile-time error to attempt to override or hide a final method.
A private method and all methods declared immediately within a final class (§8.1.1.2) behave as if they are final, since it is impossible to override them.
It is a compile-time error for a final method to be declared abstract.
A method can be declared final to prevent subclasses from overriding or hiding it. It is a compile-time error to attempt to override or hide a final method.
A private method and all methods declared immediately within a final class (§8.1.1.2) behave as if they are final, since it is impossible to override them.
It is a compile-time error for a final method to be declared abstract.
行为如同“final”的方法都无法覆写,也就无法进行子类型多态;声明为final或private的方法都被属于这类。所以除了静态方法之外,声明为final或者private的实例方法也是非虚方法。其它实例方法都是虚方法。
Java语言规范接着提到:
Java Language Specification, 3rd 写道
8.4.3.3 final Methods
(... 省略)
At run time, a machine-code generator or optimizer can "inline" the body of a final method, replacing an invocation of the method with the code in its body. The inlining process must preserve the semantics of the method invocation. In particular, if the target of an instance method invocation is null, then a NullPointerException must be thrown even if the method is inlined. The compiler must ensure that the exception will be thrown at the correct point, so that the actual arguments to the method will be seen to have been evaluated in the correct order prior to the method invocation.
Consider the example:
Here, inlining the method move of class Point in method main would transform the for loop to the form:
The loop might then be subject to further optimizations.
Such inlining cannot be done at compile time unless it can be guaranteed that Test and Point will always be recompiled together, so that whenever Point-and specifically its move method-changes, the code for Test.main will also be updated.
(... 省略)
At run time, a machine-code generator or optimizer can "inline" the body of a final method, replacing an invocation of the method with the code in its body. The inlining process must preserve the semantics of the method invocation. In particular, if the target of an instance method invocation is null, then a NullPointerException must be thrown even if the method is inlined. The compiler must ensure that the exception will be thrown at the correct point, so that the actual arguments to the method will be seen to have been evaluated in the correct order prior to the method invocation.
Consider the example:
final class Point { int x, y; void move(int dx, int dy) { x += dx; y += dy; } } class Test { public static void main(String[] args) { Point[] p = new Point[100]; for (int i = 0; i < p.length; i++) { p[i] = new Point(); p[i].move(i, p.length-1-i); } } }
Here, inlining the method move of class Point in method main would transform the for loop to the form:
for (int i = 0; i < p.length; i++) { p[i] = new Point(); Point pi = p[i]; int j = p.length-1-i; pi.x += i; pi.y += j; }
The loop might then be subject to further optimizations.
Such inlining cannot be done at compile time unless it can be guaranteed that Test and Point will always be recompiled together, so that whenever Point-and specifically its move method-changes, the code for Test.main will also be updated.
这里提到“final方法”可以在运行时得到内联。其实所有非虚方法在运行时都可以安全的被内联。
一个保守的JVM可以如上述说明一样在运行时对非虚方法的调用进行内联优化;而一个激进优化的JVM则可以更进一步,将源码中声明为虚方法、但在运行时的某个时间点可以证明(例如通过类层次分析(CHA))该方法只有一个可能的调用目标时,仍然可以将调用目标内联到调用者中。现在在桌面与服务器上的主流高性能JVM,如Oracle (Sun) HotSpot、IBM J9、Oracle (BEA) JRockit等,都会做这样的激进优化。因此在开发桌面与服务器端Java程序时没有必要为了提到性能而特意将方法声明为final的。
关于HotSpot VM对final的处理,可以参考HotSpot Internals wiki的Virtual Calls一篇:
引用
It is legal for an invokevirtual bytecode to refer to a final method. A final method need not have a vtable slot allocated. This means that, after linking, an invokevirtual bytecode might in fact collapse into the equivalent of an invokestatic bytecode. The interpreter is prepared to do this.
Anders Hejlsberg曾经在多个场合提到虚方法是无法内联的(例如这个Artima的访谈);确实CLR是无法内联任何虚方法调用,但那只是CLR的实现限制而已。这点上高性能JVM比CLR要先进(且复杂)许多。
来看看Oracle/Sun JDK里的HotSpot VM是如何做初步的是否能静态绑定的:
bool methodOopDesc::can_be_statically_bound() const { if (is_final_method()) return true; return vtable_index() == nonvirtual_vtable_index; }
也就是,如果某个Java方法是final的或者不是虚方法的话,它就可以做静态绑定。
-------------------------------------------
Java虚拟机规范第二版中定义了四种不同的字节码指令来处理Java程序中不同种类的方法的调用。包括,
· invokestatic - 用于调用类(静态)方法
· invokespecial - 用于调用实例方法,特化于super方法调用、private方法调用与构造器调用
· invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法)
· invokeinterface - 用于调用接口方法
其中,invokestatic与invokespecial调用的目标必然是可以静态绑定的,因为它们都无法参与子类型多态;invokevirtual与invokeinterface的则一般需要做运行时绑定,JVM实现可以有选择的根据final或实际运行时类层次或类型反馈等信息试图进行静态绑定。
===========================================================================
那么Java中的实例构造器是不是“静态方法”呢?从Java语言规范中给出的“静态方法”的定义来看,答案是“否”——首先从Java语言规范对“方法”的定义来说,构造器根本不是“方法”;其次,实例构造器有一个隐式参数,“this”,在实例构造器中可以访问“this”,可以通过“this”访问到正在初始化的对象实例的所有实例成员。
Java语言规范中关于构造器的说明中提到:
Java Language Specification, 3rd 写道
8.8 Constructor Declarations
A constructor is used in the creation of an object that is an instance of a class:
(... 省略)
Constructor declarations are not members. They are never inherited and therefore are not subject to hiding or overriding.
A constructor is used in the creation of an object that is an instance of a class:
(... 省略)
Constructor declarations are not members. They are never inherited and therefore are not subject to hiding or overriding.
实例构造器无法被隐藏或覆写,不参与多态,因而可以做静态绑定。从这个意义上可以认为实例构造器是“静态”的,但这种用法与Java语言定义的“静态方法”是两码事。
另外需要注意的是,Java语言中,实例构造器只能在new表达式(或别的构造器)中被调用,不能通过方法调用表达式来调用。new表达式作为一个整体保证了对象的创建与初始化是打包在一起进行的,不能分开进行;但实例构造器只负责对象初始化的部分,“创建对象”的部分是由new表达式本身保证的。
举个例子,下面的Java代码
public class ConstructorDemo { private int value; public ConstructorDemo(int i, Object o) { this.value = i; } public static void main(String[] args) { ConstructorDemo demo = new ConstructorDemo(2, args); } }
被编译为class文件后,实例构造器与main()方法的内容分别为:
public ConstructorDemo(int, java.lang.Object); Code: Stack=2, Locals=3, Args_size=3 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: aload_0 5: iload_1 6: putfield #2; //Field value:I 9: return public static void main(java.lang.String[]); Code: Stack=4, Locals=2, Args_size=1 0: new #3; //class ConstructorDemo 3: dup 4: iconst_2 5: aload_0 6: invokespecial #4; //Method "<init>":(ILjava/lang/Object;)V 9: astore_1 10: return
先从main()方法开始看。
第一条指令是new,用于创建出ConstructorDemo类型的一个空对象,执行过后指向该对象的引用被压到操作数栈上。
第二条指令是dup,将操作数栈顶的值复制一份压回到栈顶;其中dup出来的一份用于作为隐式参数传到实例构造器里去(对应后面的invokespecial),原本的一份用于保存到局部变量去(对应后面的astore_1)。
第三条指令是iconst_2,将常量2压到操作数栈上,作为ConstructorDemo实例构造器的第一个显式参数。
第四条指令是aload_0,将main()方法的参数args作为ConstructorDemo实例构造器的第二个显式参数。
第五条指令是invokespecial,调用ConstructorDemo实例构造器。再次留意,前面已经传了三个参数,分别是new出来的实例的引用、常量2与main()的参数args。该指令执行过后,操作数栈顶就只剩下dup前通过new得到的引用。
第6条指令是astore_1,将操作数栈顶的引用保存到局部变量1中。执行过后操作数栈空了。
最后一条指令是return,结束main()方法的执行并返回。
然后从ConstructorDemo的实例构造器来看。
第一条指令是aload_0,将第一个参数(不管是隐式还是显式参数)压到操作数栈上。从main()的调用序列可以看到第一个参数是刚new出来的对象实例的引用,对这个构造器来说也就是“this”。
第二条指令是invokespecial,调用Object的实例构造器。前一条指令的“this”就是这个调用的参数。执行过后操作数栈就空了。
第三条指令又是aload_0,再次将“this”压到操作数栈上。
第四条指令是iload_1,将第二个参数压到操作数栈上,也就是i。
第五条指令是putfield,将i赋值给this.value。执行过后操作数栈又空了。
最后一条指令是return,结束该实例构造器的执行并返回。
这个例子的注意点在于:
1、Java的实例构造器只负责初始化,不负责创建对象;Java虚拟机的字节码指令的设计也反映了这一点,有一个new指令专门用于创建对象实例,而调用实例构造器则使用invokespecial指令。
2、“this”是作为实例构造器的第一个实际参数传入的。
相信能区分“静态方法”与“静态绑定”中的“静态”之后,就不会再将Java中的实例构造器看作是“静态方法”了。
评论
37 楼
ArtShell
2013-12-10
很多问题都能在博客的文章中找到答案!
36 楼
RednaxelaFX
2011-07-21
gooooooooa 写道
invokespecial #4; //Method "<init>":(ILjava/lang/Object;)V
这段是不是写错了? 参数是Object?
public ConstructorDemo(int i, Object o) {
this.value = i;
}
不是还有个int?
有个理解不知道对不对:
Constructor 是看不到的,而 instance initializer 就是一段初始化代码 比如
上面的 this.value = i; ??????
这段是不是写错了? 参数是Object?
public ConstructorDemo(int i, Object o) {
this.value = i;
}
不是还有个int?
有个理解不知道对不对:
Constructor 是看不到的,而 instance initializer 就是一段初始化代码 比如
上面的 this.value = i; ??????
你可能需要把显示放大点。签名里的两个参数的类型分别是:
I
Ljava/lang/Object;
那个I就是int。
35 楼
gooooooooa
2011-07-21
invokespecial #4; //Method "<init>":(ILjava/lang/Object;)V
这段是不是写错了? 参数是Object?
public ConstructorDemo(int i, Object o) {
this.value = i;
}
不是还有个int?
有个理解不知道对不对:
Constructor 是看不到的,而 instance initializer 就是一段初始化代码 比如
上面的 this.value = i; ??????
这段是不是写错了? 参数是Object?
public ConstructorDemo(int i, Object o) {
this.value = i;
}
不是还有个int?
有个理解不知道对不对:
Constructor 是看不到的,而 instance initializer 就是一段初始化代码 比如
上面的 this.value = i; ??????
34 楼
C_J
2011-04-19
无意中翻到zangxt的帖子,就贴出来了。
后来发现不能编辑也不能删回复的。
后来发现不能编辑也不能删回复的。
33 楼
C_J
2011-04-19
4 楼 left.jessica 2010-09-26 引用
hjg1988 写道
引用
《java编程思想》中文第四版96页:
总结一下对象的创建过程,假设有个名为Dog的类:
1.即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成
是静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
这里我并没有看明白作者为什么说构造器实际上是静态方法。但是我们知道,静态方法是不能使用this的。因此,"构造器实际上也是静态方法"这点很好否定。
我的理解是,静态方法是不需要通过类的实例来调用的,而是类被加载后放在栈中的,是属于“类方法”。非静态方法是要通过类的实例才调用的,是属于 “对象方法”。从这个角度来讲,构造方法可以认为是静态的,因为在调用构造方法时必定是还未实例化的,而实例化后就不能再调用构造方法了。这样理解不知道对不对
我做了个测试程序:
Java代码 收藏代码
1. public static void main(String[] args) {
2.
3. try{
4. Class c = Class.forName("mypackage.SK");
5. Constructor[] cc = c.getConstructors();
6. Method[] m = c.getMethods();
7.
8. int b = cc[0].getModifiers();
9. System.out.println(b);
10. System.out.println(Integer.toHexString(b));
11. System.out.println("final:" + Modifier.isFinal(b));
12. System.out.println("public:" + Modifier.isPublic(b));
13. System.out.println("abstract:" + Modifier.isAbstract(b));
14. System.out.println("native:" + Modifier.isNative(b));
15. System.out.println("protected:" + Modifier.isProtected(b));
16. System.out.println("Static:" + Modifier.isStatic(b));
17.
18.
19. catch(ClassNotFoundException cl) {
20. System.out.println("ClassNotFound");
21. }
22. catch(SecurityException se) {
23. System.out.println("SecurityException");
24. }
25.
26. }
public static void main(String[] args) {
try{
Class c = Class.forName("mypackage.SK");
Constructor[] cc = c.getConstructors();
Method[] m = c.getMethods();
int b = cc[0].getModifiers();
System.out.println(b);
System.out.println(Integer.toHexString(b));
System.out.println("final:" + Modifier.isFinal(b));
System.out.println("public:" + Modifier.isPublic(b));
System.out.println("abstract:" + Modifier.isAbstract(b));
System.out.println("native:" + Modifier.isNative(b));
System.out.println("protected:" + Modifier.isProtected(b));
System.out.println("Static:" + Modifier.isStatic(b));
catch(ClassNotFoundException cl) {
System.out.println("ClassNotFound");
}
catch(SecurityException se) {
System.out.println("SecurityException");
}
}
而SK:
Java代码 收藏代码
1. public class SK {
2.
3. public SK(){
4.
5. }
6.
7. }
public class SK {
public SK(){
}
}
结果是static:false
hjg1988 写道
引用
《java编程思想》中文第四版96页:
总结一下对象的创建过程,假设有个名为Dog的类:
1.即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成
是静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
这里我并没有看明白作者为什么说构造器实际上是静态方法。但是我们知道,静态方法是不能使用this的。因此,"构造器实际上也是静态方法"这点很好否定。
我的理解是,静态方法是不需要通过类的实例来调用的,而是类被加载后放在栈中的,是属于“类方法”。非静态方法是要通过类的实例才调用的,是属于 “对象方法”。从这个角度来讲,构造方法可以认为是静态的,因为在调用构造方法时必定是还未实例化的,而实例化后就不能再调用构造方法了。这样理解不知道对不对
我做了个测试程序:
Java代码 收藏代码
1. public static void main(String[] args) {
2.
3. try{
4. Class c = Class.forName("mypackage.SK");
5. Constructor[] cc = c.getConstructors();
6. Method[] m = c.getMethods();
7.
8. int b = cc[0].getModifiers();
9. System.out.println(b);
10. System.out.println(Integer.toHexString(b));
11. System.out.println("final:" + Modifier.isFinal(b));
12. System.out.println("public:" + Modifier.isPublic(b));
13. System.out.println("abstract:" + Modifier.isAbstract(b));
14. System.out.println("native:" + Modifier.isNative(b));
15. System.out.println("protected:" + Modifier.isProtected(b));
16. System.out.println("Static:" + Modifier.isStatic(b));
17.
18.
19. catch(ClassNotFoundException cl) {
20. System.out.println("ClassNotFound");
21. }
22. catch(SecurityException se) {
23. System.out.println("SecurityException");
24. }
25.
26. }
public static void main(String[] args) {
try{
Class c = Class.forName("mypackage.SK");
Constructor[] cc = c.getConstructors();
Method[] m = c.getMethods();
int b = cc[0].getModifiers();
System.out.println(b);
System.out.println(Integer.toHexString(b));
System.out.println("final:" + Modifier.isFinal(b));
System.out.println("public:" + Modifier.isPublic(b));
System.out.println("abstract:" + Modifier.isAbstract(b));
System.out.println("native:" + Modifier.isNative(b));
System.out.println("protected:" + Modifier.isProtected(b));
System.out.println("Static:" + Modifier.isStatic(b));
catch(ClassNotFoundException cl) {
System.out.println("ClassNotFound");
}
catch(SecurityException se) {
System.out.println("SecurityException");
}
}
而SK:
Java代码 收藏代码
1. public class SK {
2.
3. public SK(){
4.
5. }
6.
7. }
public class SK {
public SK(){
}
}
结果是static:false
32 楼
RednaxelaFX
2011-02-27
william_ai 写道
引用
2、“this”是作为实例构造器的第一个实际参数传入的。
在JVM标准2中也有介绍。(实例的初始化方法也是实例的方法)
引用
On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language)
嗯这段在上面的原文里也引用过了,3.6.1的蓝字。
31 楼
william_ai
2011-02-27
在JVM标准2中,也明确指出了
引用
A constructor cannot be abstract, static, final, native, or synchronized.
30 楼
william_ai
2011-02-27
实例的构造方法,理解成实例的初始化方法,就不会有那么绕了。(JVM标准2中也是这么介绍的。)
抛一个问题,类和接口的初始化方法是不是static的?什么时候调用?
在JVM标准2中也有介绍。(实例的初始化方法也是实例的方法)
抛一个问题,类和接口的初始化方法是不是static的?什么时候调用?
引用
2、“this”是作为实例构造器的第一个实际参数传入的。
在JVM标准2中也有介绍。(实例的初始化方法也是实例的方法)
引用
On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language)
29 楼
RednaxelaFX
2011-02-25
lostdog 写道
换个角度,从jvm内部看,方法是被保存在JTOC还是TIB中,<init>被保存在JTOC中,也可以认为是静态的。
JTOC和TIB都是Jikes RVM/MRP才使用的叫法。别的JVM实现也不叫这个…
28 楼
lostdog
2011-02-25
换个角度,从jvm内部看,方法是被保存在JTOC还是TIB中,<init>被保存在JTOC中,也可以认为是静态的。
27 楼
rjhw
2011-02-11
rjhw 写道
实例初始化器与实例变量初始化器在编译时会与构造器一起被收集到<init>()方法中
这是你文章中的一句话
在jvm中对构造器方法中通过使用
aload_0和invokespecial方法来调用
<init>方法
从而和你说的这句话有矛盾
这是你文章中的一句话
在jvm中对构造器方法中通过使用
aload_0和invokespecial方法来调用
<init>方法
从而和你说的这句话有矛盾
不好意思 我看错了 没 问题
26 楼
rjhw
2011-02-11
实例初始化器与实例变量初始化器在编译时会与构造器一起被收集到<init>()方法中
这是你文章中的一句话
在jvm中对构造器方法中通过使用
aload_0和invokespecial方法来调用
<init>方法
从而和你说的这句话有矛盾
这是你文章中的一句话
在jvm中对构造器方法中通过使用
aload_0和invokespecial方法来调用
<init>方法
从而和你说的这句话有矛盾
25 楼
RednaxelaFX
2010-12-23
cantellow 写道
我英语不好,咋办?
学。
当然也可以指望别人。毕玄 @bluedavy 有向刘总编 @turingbook 建议引进这本书。如果有人翻译的话就可以坐等中文版了。但我仍然强烈推荐阅读第一手资料。
24 楼
cantellow
2010-12-23
RednaxelaFX 写道
cantellow 写道
另,关于JVM,楼主还有什么好书推荐。
Oracle JRockit: The Definitive Guide
我英语不好,咋办?
23 楼
cantellow
2010-12-23
我来揣测一下Bruce Eckel当初是怎么把构造器理解成为静态的。
当时sun实现构造器就是为了给开发人员初始化变量的一种手段,可能其中某些特性与静态方法符合,比如无法覆盖,不参与多态等等,所以他就认为构造器是静态方法。
其实严格来说,构造器存在于静态方法,实例方法之外的第三种方法,它会被虚拟机<init>静态方法调用。
这是我的理解。
当时sun实现构造器就是为了给开发人员初始化变量的一种手段,可能其中某些特性与静态方法符合,比如无法覆盖,不参与多态等等,所以他就认为构造器是静态方法。
其实严格来说,构造器存在于静态方法,实例方法之外的第三种方法,它会被虚拟机<init>静态方法调用。
这是我的理解。
22 楼
RednaxelaFX
2010-12-23
cantellow 写道
另,关于JVM,楼主还有什么好书推荐。
Oracle JRockit: The Definitive Guide
21 楼
cantellow
2010-12-23
我就是当初的提问者:http://www.iteye.com/problems/14154
惭愧呀。
当初在本科时期的确是被《Thinking In Java》误导所致。
工作快一年了,想什么时候再翻翻《深入java虚拟机》看看,毕竟很多东西都忘了。
另,关于JVM,楼主还有什么好书推荐。
惭愧呀。
当初在本科时期的确是被《Thinking In Java》误导所致。
工作快一年了,想什么时候再翻翻《深入java虚拟机》看看,毕竟很多东西都忘了。
另,关于JVM,楼主还有什么好书推荐。
20 楼
ouchxp
2010-12-23
ZangXT 写道
页码忘记了
Even though it doesn’t explicitly use the static keyword, the constructor is actually a static method. So the first time an object of type Dog is created, or the first time a static method or static field of class Dog is accessed, the Java interpreter must
locate Dog.class, which it does by searching through the classpath.
Even though it doesn’t explicitly use the static keyword, the constructor is actually a static method. So the first time an object of type Dog is created, or the first time a static method or static field of class Dog is accessed, the Java interpreter must
locate Dog.class, which it does by searching through the classpath.
看书不仔细 所以没被误导 晕.....
19 楼
mercyblitz
2010-06-23
RednaxelaFX 写道
mercyblitz 写道
RednaxelaFX 写道
mercyblitz 写道
构造器是用于初始化对象状态,并且隐含了this引用。
我一直在想,能不能动态地分析所有的字节码文件,从而达到多态方法的inline,毕竟所有的对象都是字节码定义的。
我一直在想,能不能动态地分析所有的字节码文件,从而达到多态方法的inline,毕竟所有的对象都是字节码定义的。
分析的对象不是“字节码”而是“类层次结构”;做了这个分析后,多态方法也照样可以激进内联。实际的高性能JVM中也是这么做的。
类层次结构不在字节码体现?
Class文件中字节码只用于表示代码逻辑,类层次结构是在字节码之外的元信息里记录的。例子在之前一帖的演示稿里有,有兴趣的话可以参考。
嗯,谢谢,确实不太记得了。
18 楼
RednaxelaFX
2010-06-23
mercyblitz 写道
RednaxelaFX 写道
mercyblitz 写道
构造器是用于初始化对象状态,并且隐含了this引用。
我一直在想,能不能动态地分析所有的字节码文件,从而达到多态方法的inline,毕竟所有的对象都是字节码定义的。
我一直在想,能不能动态地分析所有的字节码文件,从而达到多态方法的inline,毕竟所有的对象都是字节码定义的。
分析的对象不是“字节码”而是“类层次结构”;做了这个分析后,多态方法也照样可以激进内联。实际的高性能JVM中也是这么做的。
类层次结构不在字节码体现?
Class文件中字节码只用于表示代码逻辑,类层次结构是在字节码之外的元信息里记录的。例子在之前一帖的演示稿里有,有兴趣的话可以参考。
发表评论
-
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 16273以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10450先看看下面这个代码例子, 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 ... -
基于LLVM实现VM的JIT的一些痛点
2014-01-07 17:25 0同事Philip Reames Sanjoy Das http ... -
tailcall notes
2013-12-27 07:42 0http://blogs.msdn.com/b/clrcode ... -
《自制编程语言》的一些笔记
2013-11-24 00:20 0http://kmaebashi.com/programmer ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22390(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局 (0): 拿在手上的是什么
2013-11-04 18:22 21489(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 21871之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ...
相关推荐
每个类可以有一个默认构造函数(无参数的构造器),也可以根据需求定义带参数的构造函数。构造方法的主要作用包括: 1. 初始化类的成员变量。 2. 执行必要的设置工作,比如资源分配。 3. 链接到其他构造函数,以实现...
Java编程语言中,类的初始化过程涉及到静态块(static block)、实例块(instance block)以及构造器(constructor)。这些块的执行顺序对于理解和编写复杂的Java程序至关重要,特别是在涉及继承关系时。下面我们将...
java 静态_非静态 字段_方法_代码块 子类父类构造_初始化顺序! 三个class 让你清清楚楚 第一个class java代码如下: package initialOrder; class Parent { // 静态变量 public static String p_StaticField...
在方法中,`super`用于调用超类中被重写的方法,而在构造器中,`super`用于调用超类的构造器,确保子类实例化时,超类的状态也被正确初始化。例如: ```java class Mammal { void getBirthInfo() { System.out....
- 实例构造器(非静态构造器)是为每个新创建的类实例服务的,而静态构造器只为类本身服务。 - 实例构造器可以通过`new`关键字来调用,而静态构造器不能被直接调用。 - 实例构造器可以被重载,而静态构造器不能。...
在 Java 编程语言中,构造器是一种特殊的方法,用于初始化对象的创建。它是 Java 类中最重要的一个概念。下面将深入讨论构造器的机理、执行顺序、作用及与其他概念的区别。 一、构造器的机理 构造器是一种特殊的...
在Java编程语言中,构造器(Constructor)是一个特殊的方法,主要负责对象的初始化工作。当创建一个新的类实例时,构造器会被自动调用。构造器的名称必须与类名完全相同,且没有返回类型,包括void。理解并熟练运用...
对象是类的实例,构造器是创建对象的特殊方法。 类和对象 在 Java 中,每个对象都是一个类的实例。类是对对象的抽象,定义了对象的属性和行为。类中定义的变量称为实例变量,方法称为实例方法。 构造器 构造器是...
1. **静态工厂方法有名称**:与构造器只能通过类名来区分不同实例化方式不同,静态工厂方法可以根据功能命名,使得代码更易读,更直观地理解方法的作用。 2. **静态工厂方法不用在每次调用时创建新对象**:静态工厂...
对于第一种方法,可以使用私有构造器来实例化公有的静态 final 域,然后使用静态方法 getInstance 返回对同一个对象的引用。对于第二种方法,可以使用私有构造器来阻止对象的生成,然后使用公有的静态方法来访问类的...
- **静态构造器不存在**:Java中没有静态构造器的概念,所有构造器都用于创建对象实例。 #### 七、示例代码解析 在提供的部分代码中,可以看到如下示例: ```java public class Platypus extends Mammal { String...
本文将深入探讨Spring中实例化Bean的三种主要方式:构造器注入、静态工厂方法注入以及实例工厂方法注入。 #### 1. 构造器注入(Constructor Injection) 构造器注入是指通过调用Bean类的构造器来创建Bean实例。...
3. 调用`Method`对象的`invoke()`方法:传入null作为第一个参数,因为静态方法不需要实例对象。第二个参数是传递给静态方法的实际参数。 以下是一个简单的示例,演示如何使用反射调用静态方法: ```java public ...
在Python编程中,类的方法分为三种类型:实例方法、类方法和静态方法,它们各自有特定的使用场景和特点。 1. 实例方法 实例方法是通过实例对象调用的方法,通常第一个参数是`self`,它指向调用该方法的实例对象。...
在 `Table` 类和 `Cupboard` 类的构造器中,可以看到静态变量的初始化会导致它们关联的实例方法 `f()` 被调用。例如,`Table` 类的构造器在创建静态变量 `b2` 时调用了 `b2.f(1)`,这将打印出 "f(1)"。同样,`...
在`C`类的`main`方法中,创建第二个`B`类实例时,由于静态代码块只执行一次,所以不会再输出1和"a",而是直接执行`B`类构造器两次,输出两次"b"。因此,完整的输出结果为:1a2b2b。 接着,我们来看构造方法的执行...
局部变量是在方法、构造器或者块中声明的,它们的作用域仅限于声明它们的代码块。一旦代码块执行完毕,局部变量就会被销毁。局部变量不能在类的外部直接访问,也不能在其他方法中使用,除非将它们作为参数传递。 3...
1. **名称可变性**:静态工厂方法可以拥有与类名不同的名称,这样可以根据方法名的不同返回不同类型的实例,比构造器更灵活。 2. **无需公开构造器**:如果类的实例不应直接通过`new`关键字创建,静态工厂可以提供一...
总结,`this`和`static`是Java面向对象编程中的重要元素,`this`用于表示当前对象引用,方便访问实例变量和调用构造器;而`static`则用于定义类级别的成员,这些成员独立于对象存在,可以被所有实例共享。理解和熟练...
由于JavaScript中的函数同时具有普通函数和构造器的特性,因此静态方法和属性的实现也显得非常自然和直观。在实践中,合理地使用静态方法和属性可以增强代码的复用性和组织性。 总结一下,在JavaScript中实现静态...