深入JVM---JVM命令---invokespecial

19.3 指令invokespecial 
Invokespecial和invokeirtual的主要区别在于: invokespcial通常(只有一个例子) 根据引用的类型选择方法,而不是根据对象的类来选择,换句话说。它使用静态绑定而不是动态绑定。在下列使用invokespecial的三种情况中,动态绑定并不会产生所预期的效果。 
19.3.1 指令invokespecial和<init>()方法 
<init>() 方法(或者实例初始化方法)是编译器为构造方法和实例变量初始化方法放置代码的地方,类不会为源文件中的每个构造方法提供一个<init>()方法。如果没有在源文件中显示声明一个构造方法。编译器就会为这个类产生一个默认的无参的构造方法。这个默认构造方法通常以class文件中的一个<init>()方法结束。因此就像每个类都至少会有一个构造方法一样。每个类都至少会有一个<init>()方法。这些方法通常是用invokespecial调用。 
只有创建一个新的势力的时候,才调用 <init>()方法。新创建的对象继承路径中,每个类都至少会调用一个<init>() 方法,而且继承路径中的任何一个类都有可能调用多个<init>()方法。 
使用invokespecial来调用<init>()的原因在于:子类的<init>()方法需要拥有调用超类的<init>()方法的能力。这就揭示了为什么一个对象实例化时,有多个<init>()方法被调用的原因。虚拟机调用对象类中的<init>()方法。这个过程贯穿于对象的这个生命周期。 
例如,考虑下列代码: 
class Dog{ 


class CockerSpaniel extends Dog{ 
public static void main(String args[]){ 
CockerSpaniel bootsie = new CockerSpaniel(); 


当调用main()方法的时候,虚拟就就会为新的CockerSpanicl对象分配空间,容纳后调用CockerSpaniel的默认无参的<init>()方法来初始化那段空间。那个方法将首先调用Dog的<init>()方法,然后再一次调用Object的<init>()方法。类CockerSpanie的main()方法的字节码如下: 
//Create a new CockerSpaniel Object , push ref 
0 new #1 <Class CockerSpanial> 
//Invoke <init>() method on object ref 
//new CockerSpaniel(); 
3 invokespecial #3 <Method <init>() of class CockerSpaniel> 
//Note compiler didn’t store resulting ref in a var 
//representing bootsia, because it war neer used 
6 return //return void from main 
CockerSpaniel类的main()方法为新的CckerSpaniel对象分配内存,并使用new指令将分配的内存初始化为默认的初始化值(“#1” 之处了需要实例化的类的常量入口, 这里特指CockerSpaniel类)。New指令吧一个纸箱CockerSpaniel对象的引用压入栈。然后main()方法使用invokerspecial指令通过该对象引用调用类CockerSpaniel的<init>()方法(“#3”指出了常量池入口,其中包含了对CockerSpaniel的<init>()方法的引用)。Java虚拟机把一个新的栈帧压入了java栈,然后把对象引用赋给新的栈帧中的局部变量0.类CockerSpaniel   的 
<init>()方法的字节码如下: 

0 aload_0 //push object ref from local var 1 Invoke Dog’s <init>() on object ref 
1 invokespecial $4 <Method <init>() of class Dog> 
4 return //return void from CockerSpaniel’s <init>() 

如前所述,这里的<init>()方法相当于编译器为类CockerSpaniel自动糊产生的默认无参构造方法。这个方法首先把从局部变量0中取出来的已被初始化的对象引用压入栈。然后通过这个引用调用Dog的<init>()方法(#4 之处了常量池入口,其中那个包含了指向了Dog的<init>()方法的引用)。Dog类的<init>()方法的字节码如下所示: 

0 aload_0 //push obj ref from local var 0 
                  //invoke object’s <init>() method on obj ref 
1 invkespecial #3 <Method <init>()  of class java.lang.Object> 
0 return  //return void from Dog’s <init>() 

这里的<init>()方法相当于编译器Dog自动产生的默认无参构造方法。这个方法相当于编译器为类Dog自动产生 的默认无参构造方法。 这个方法首先从局部变量0中取出已被初始化的对性爱那个的引用压入栈。然后通过这个引用调用Object的<init>()方法(这里#3指出来常量池的入口,其中包含指向Object的<init>()方法的引用。这个常量池并不是指向类CockerSpaniel的方法的那个常量池,每个类都有自己的常量池)。当三个<init>()方法都返回后。出main()新建的cockerspaniel对象才完成了初始化工作。 
由于每个类至少有一个<init>()方法。类的<init>()方法拥有相同的特征签名是很普遍的现象。(方法的特征签名是指他的名字,参数的数量和类型)。例如,CockerSpaniel类继承路径中的三个<init>()方法。它们的特征签名相同。CockerSpaniel,Dog和Object东圃有个一个名为<init>()的无参方法。 
Invokevirtual指令会执行动态绑定和调用CockerSpanicl的<init>()方法,所以从CockerSpaniel的<init>()方法使用invokevirtual指令调用Dog的<init>()方法是不肯能的。然而通过使用invokespecial指令,CockerSpaniel的<init>()方法能够调用Dog类的<init>()方法。因为放在CockerSpaniel的class文件(常量池入口#4)中的引用的类型为Dog。 

19.3.2 指令invokespecial和私有方法。 
当处理自由实例方法的时候,必需玉虚子类使用于超类中实例方法通用的特征签名类声明实例方法(invokespecial只用来调用私有实例方法,不能调用私有类方法,私有类方法由invokestatic指令调用)例如在下列代码中,interestingMethod90是超累中的私有方法。子类对其具有包访问权限。 
class Superclass{ 
    private void interestingMethod(){ 
    System.out.println("Superclass's intersting method."); 
    } 
    
    void exampleMethod(){ 
    interestingMethod(); 
    } 


class Subclass extends Superclass{ 
void interstingMethod(){ 
System.out.println("Subclass's interesting method."); 


public static void main(String args[]){ 
Subclass me = new Subclass(); 
me.exampleMethod(); 




如前所述。当调用像前面所定义的subclass中的main()方法的时候, 虚拟机会实例化一个新的Subclass对象, 然后调用exampleMethod()方法。类Subclass的main(0方法的字节码如下所示: 
//cerate a new instance of class subclass, push ref 
0 new #2 <Class Subclass> 
3 dup // Duplicate ref , push duplicate 
          //Invoke <init>() method on new object Subclass me = new SubClass(); 
4 invokespacial #6 <Method <init>() of class Subcliass> 
7 astore_1  //pop object ref into local var 1 
8 aload_1  // push re from local var 1 
                    //Invoke exampleMethod() on object ref; 
                     // me.exampleMethod(); 
        9 invokeirtual #8 <method void exampleMethod() of class Superclass> 
       12 return // return void from main() 

Subclass从Superclass处继承了exampleMethod()方法,当方法main()调用subclass对象汇总的方法exampleMethod()时。它使用invokevirtual指令,正如类Superclass所定义的。Java虚拟机将会闯将一个新的栈帧并将其压入栈,然后开始执行examplemethod()的字节码。方法exampleMethod()的字节码如下所示: 
0 aload_0 //push obj ref from local var 1 
                   //invoke interestingMethod() on obj ref 
                // interestingMethod()l 
1 invokespecial #7 <method void interestingMethod() of Superclass> 
4 return // return void from exampleMethod() 

需要注意的是。 方法exampleMethod()首相将赋给局部变量0的引用压入栈(隐含参数tims被传个所有的实例方法) 然后使用invokespecial指令通过这个引用调用interstingMethod)方法。尽管这里说的对象是Subclass类的实例,而且Subclass类中的interestingmethod()方法也是能够访问的,但是java虚拟机最终还是调用了Superclas类中的interestingMethod()方法。 
程序争取输入为” Subclass's interesting method.” 如果使用指invokevirtual而不是指令invokespecial来调用interestingMethod(). 那么程序的输出结果会是哪个interestingMethod()。这里对象实际所述的类是subclass。 因此虚拟机会使用Subclass的interestingMethod。因此就会调用Superclass的interestingMethod()版本呢。 

19.3.3 指令invokespecial和私有方法。 
当使用super关键字来调用方法时(例如super.someMethod()),尽管当前类重载了该方法。但是使用者真正希望调用的还是超累的方法。再说一次,指invokevirual只能调用当前类的方法。无法使用超累的方法。例如: 
class Cat{ 
void someMethod(){ 



class TabbyCat extends Cat{ 
void someMethod(){ 
super.someMethod(); 



类TabbyCat的someMethod()方法的字节码如下: 
0 aload_0 // push obj ref from local var 0 
                   // invoke cat’s someMethod() on obj ref 
         1  invokespecial #4 <method voiud someMethod() of class Cat> 
4 return // return void from TabbyCat’s someMethod() 

如果这里使用invokevirtual指令,那么将会调用TabbyCat的someMethod()方法。但是因为这里使用了invokespecial指令。并且常量池入口(这里是#4)指明了调用类Cat中声明的someMethod()方法。因此java虚拟机将会调用超累的someMethod(). 
Java 虚拟机是否是使用静态绑定来执行invokespecial指令(或者一种特殊的动态绑定)取决于所指向的类是否制定了ACC_SUPER标志。JDK1.0.2版本以前的各种版本中。Invokespecial指令的名称为invokenonvirtual。而且总会导致静态绑定的使用。结果却是invokenonvirtual所坚持的静态绑定无法办证所有情况下java语义的正确使用。在JDK1.0.2中。Invokenonvirtual指令更名为invokespecial。它的语义也改变了。此外,Java class文件中的access_flags项中还加入了一个新的标志:ACC_SUPER. 
Class文件的ACC_SUPER标志项指明。Java虚拟机应该使用哪一种语义来执行class文件字节码所遇到的invokespecial指令。如果没有设置ACC_SUPER标志,虚拟机将会使用旧语义(invokenovirtual);若果设置了ACC_SUPER标志。 虚拟机会使用新语义,所有新版本的java编译器都会在省城的class文件中默认设置ACC_SUPER标志。 
根据旧语义。当执行invokespecial指令时。虚拟机将会在二和情况下使用静态绑定。而新语义除了调用超类方法外,其他的情况一律使用静态绑定。 
根据新语义。当java虚拟机解析一个invokespecial指令中指向超累方法的符号引用时。他会动态搜索当前的超累。找到离得最近的超类中的钙方法的实现,在大多数情况下,虚拟机很可能发现最近方法实现粗在于符号引用列的超类中。但是虚拟机也可能在另外一个不同的超类中发现最近的方法实现。 
例如,如果创建了一颗包含三个类的继承数(Animal,Dog CockerSpaniel). 假如Dog是Animal的子类。CockerSpaniel是Dog的子类。在CockerSpaniel类中定义了一个方法。它使用invokespecial来调用一个名为walk()的方法, 但是dog类没有定义该方法。此时,CockerSpaniel类中指向walk()方法的符号引用将会把animal类作为它的类。当执行CockerSpaniel类中invokespecial指令时虚拟机会进行动态选择,并巧用Animal类的walk()方法。 
如果后来的Dog类中加入了walk()方法。斌企鹅重新编译了Dog类。但是没有重新编译CockerSpaniel类。它的指向超类的walk()方法的符号引用仍然会将Animal作为自己的类。尽管爱Dog类中已经有了walk()方法的实现。不过,当执行CockerSpaniel类中的invokespecial指令时,虚拟机将会动态选择并且调用Dog类中的walk()方法实现。 

你可能感兴趣的:(jvm)