为何JAVA虚函数(虚方法)会造成父类可以"访问"子类的假象?
首先,来看一个简单的JAVA类,Base。
1 public class Base {
2 String str = "Base string";
3 protected void show( ){
4 System.out.println(str);
5 init();
6 }
7 protected void init(){
8 System.out.println("Base init");
9 }
10 }
然后,从Base类中派生一个子类Sub。并且在Sub类中的测试方法mytest中调用show方法,该方法是从父类Base中继承来的,其中,show方法里面访问了名为"str"的实例字段。问题是,现在Base类和Sub类中,都定义了各自的“str”实例字段,按照“如果子类定义了与父类中同名的字段,那么子类的字段将隐藏父类的字段”,请注意这类的“隐藏”二字而不是“覆盖”或“重写”之类的。那么show方法中“System.out.println(str)”打印的到底是Sub中的str还是Base中的str呢?
public class Sub extends Base {
String str = "Sub string"; @Test
public void mytest(){
Sub sub = new Sub();
sub.show();
}
}
行mytest测试方法,结果如下:
Base string
Base init
可见,show方法中访问的是Base中的str字段。这是为什么呢?也许你会觉得好笑,但是如果结合JVM底层机制,这个还是有些玄机的。为了解释这个现象,我画了了一个sub对象的内部布局草图。
如下图所示,仅考虑str实例字段时,sub对象中同时存在着两个名字相同但是来源不同的str字段。因为“System.out.println(str)”相当于“System.out.println(this.str)”,那么“this.str”到底代表哪一个呢?因为两个str都是this对象的成员。
通过查看Class反汇编文件可以发现,其实在class文件中,show方法中保存的是str的符号引用,该引用只可能是其自身或是其父类(接口)中字段的引用,因此不可能是对子类的字段引用(因为父类根本就不知道子类的存在)。而在类加载或程序运行的某个时刻的解析阶段,JVM会将该符号引用解析为直接引用,解析的顺序为,如果在该字段所属的类(从CONSTANT_Fieldref_info中可以得到)中找到名字和类型相同的字段,就是用该字段的直接引用。如果没有找到,就会继续查找其父类或接口。在本例中,在Base类中就可以找到简单名和类型都相符合的str字段,因此解析访问的就是来自Base的str的直接引用(其实就是str实例字段在对象映像中的偏移量)。
show方法中还调用了init方法,由于该方法没有被子类重写,因此此处不会有任何异议。但是如果子类重写了init方法,情况又会不同。代码如下:
public class Sub extends Base {
String st = "Sub string"; @Override
protected void init(){
System.out.println("Sub init");
}
@Test
public void mytest(){
Sub sub = new Sub();
sub.show();
}
}
再次执行mytest测试方法,可以看见show方法中调用的是Sub类的init方法。
Base string
Sub init
那么方法调用和字段方法有什么不同呢?通过查看Base类的class反汇编文件,可以看见,调用init方法的指令为invokevirtual #14 ,该指令的参数就是init方法的符号引用(Base.virtual_init:()V),在解析阶段,该符号引用也会像实例属性解析那样,被替换成方法的直接引用---方法表的偏移量(方法表中存储着实际的方法地址)。但是不同的是,invokvirtual在执行时,会使用该偏移量在方法接收者实际类型的方法表中取得该方法的实际执行地址。
如下图所示,假设Base中除了init还有其他的虚方法,假设init虚方法在虚方法表中的偏移量为1(这个偏移量就是init方法的直接引用),如果子类没有重写init方法,那么子类Sub的虚方法表中偏移量为1的地方保存的也是与父类Base相同的方法地址。因此,在执行init方法调用时,还是调用的父类的init方法。
但是,当子类Sub重写了init方法时,也相应的修改了Sub的虚方法表中偏移量为1处的方法地址,使得该地址指向了子类Sub自己的init方法,因此,指向init方法调用,实际执行的是Sub自己实现的init方法,而不是父类Base的方法。
以下为C++编写的相同的测试例子。首先,如果不讲init函数声明为virtual,那么无论子类Sub是否自己定义了init版本,父类Base中调用的init永远都是父类中的init(静态绑定)。
class Base {
public:
char * str = "Base string";
void init() {
cout << "Base init" << endl;
}
void show() {
cout << str << endl;
init();
}
};
class Sub :public Base {
public:
char * str = "Sub string";
void init() {
cout << "Sub init" << endl;
}
};
int main()
{
Sub s;
s.show();
return ;
}
Base string
Base init
当为init函数添加virtual声明,使其成为一个虚函数时,此时Base类会产生和维护一个虚函数表。同理,派生的子类Sub也会有一个虚函数表,对虚函数的调用都是动态绑定的,与JAVA原理类似,都是使用虚函数在虚函数表中的索引偏移量来取得函数的实际地址。
class Base {
public:
char * str = "Base string";
virtual void init() {
cout << "Base init" << endl;
}
void show() {
cout << str << endl;
init();
}
};
Base string
Sub init
下面提供一个比较全面的例子,测试了实例字段、静态字段、虚方法、静态方法的调用机制。并分别提供了Base和Sub的反汇编代码。
/**
* Created by chen on 2016/3/21.
*/
public class Base {
String str = "Base string";
int base_int = 4;
static String static_string = "static base string";
public Base(){
System.out.println("Base.this" + this);
} protected void virtual_show( ) {
System.out.println(str);//Base string,省略了this
System.out.println(static_string);//static base string
virtual_init();//虚函数调用,会使用实际类型的虚方法表来调用,运行时绑定。
// 因为子类已经重写了这个方法,因此在方法表中调用该方法时实际是子类覆盖的方法
static_init();//静态方法调用,静态绑定,不会产生多态
static_init2();//静态绑定,不会产生多态
}
/*这是一个虚方法,会被子类重写覆盖*/
protected void virtual_init(){
System.out.println("Base virtual init");
}
/*这是一个静态方法,会被子类隐藏*/
protected static void static_init(){System.out.println("Base static init");}
/*这是一个静态方法,不会被子类隐藏*/
protected static void static_init2(){System.out.println("Base static init2");}
}
import org.junit.Test; /**
* Created by chen on 2016/3/21.
*/
public class Sub extends Base {
String str = "Sub string";//隐藏父类实例字段
static String static_string = "static Sub string";//隐藏父类静态字段 /*打印this,以父类打印的this作对比*/
public Sub(){
System.out.println("Sub.this" + this);
} /*测试父类方法中调用被覆盖的init方法,只有虚方法才会被重写,所谓的重写本质上是对虚方法表
* 中存储的原方法地址的重写、覆盖,从而可以实现多态*/
@Override
protected void virtual_init() {
System.out.println("Sub init");
}
/*@Override 静态方法的覆盖不是重写,只能说子类有一个同名的方法隐藏了父类的同名方法
* 该隐藏现象不会产生多态效果(当然,因为静态对的根本就不会出现在虚方法表中)*/
protected static void static_init(){
System.out.println("Sub static init");
} public void show_field(){
System.out.println(str);//Sub string
System.out.println(super.str);//Base string,使用super限定访问父类中被隐藏了字段
System.out.println(static_string);//static Sub string
System.out.println(Base.static_string);//static base string
} public void virtual_test(){
/*访问父类的字段*/
System.out.println(base_int);
} @Test
public void mytest(){
Sub sub = new Sub();
sub.virtual_show();
sub.show_field();
sub.static_init();//调用被
sub.static_init2(); System.out.println("---------------------------------------");
Base base = new Sub();
base.virtual_show();
}
}
Classfile /C:/Users/chen/Desktop/Base.class
Last modified 2016-3-31; size 1272 bytes
MD5 checksum 2320eb084b466a042491065dc8d9aaeb
Compiled from "Base.java"
public class Base
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #22.#42 // java/lang/Object."<init>":()V
#2 = String #43 // Base string
#3 = Fieldref #21.#44 // Base.str:Ljava/lang/String;
#4 = Fieldref #21.#45 // Base.base_int:I
#5 = Fieldref #46.#47 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Class #48 // java/lang/StringBuilder
#7 = Methodref #6.#42 // java/lang/StringBuilder."<init>":()V
#8 = String #49 // Base.this
#9 = Methodref #6.#50 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#10 = Methodref #6.#51 // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
#11 = Methodref #6.#52 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#12 = Methodref #53.#54 // java/io/PrintStream.println:(Ljava/lang/String;)V
#13 = Fieldref #21.#55 // Base.static_string:Ljava/lang/String;
#14 = Methodref #21.#56 // Base.virtual_init:()V
#15 = Methodref #21.#57 // Base.static_init:()V
#16 = Methodref #21.#58 // Base.static_init2:()V
#17 = String #59 // Base virtual init
#18 = String #60 // Base static init
#19 = String #61 // Base static init2
#20 = String #62 // static base string
#21 = Class #63 // Base
#22 = Class #64 // java/lang/Object
#23 = Utf8 str
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 base_int
#26 = Utf8 I
#27 = Utf8 static_string
#28 = Utf8 <init>
#29 = Utf8 ()V
#30 = Utf8 Code
#31 = Utf8 LineNumberTable
#32 = Utf8 LocalVariableTable
#33 = Utf8 this
#34 = Utf8 LBase;
#35 = Utf8 virtual_show
#36 = Utf8 virtual_init
#37 = Utf8 static_init
#38 = Utf8 static_init2
#39 = Utf8 <clinit>
#40 = Utf8 SourceFile
#41 = Utf8 Base.java
#42 = NameAndType #28:#29 // "<init>":()V
#43 = Utf8 Base string
#44 = NameAndType #23:#24 // str:Ljava/lang/String;
#45 = NameAndType #25:#26 // base_int:I
#46 = Class #65 // java/lang/System
#47 = NameAndType #66:#67 // out:Ljava/io/PrintStream;
#48 = Utf8 java/lang/StringBuilder
#49 = Utf8 Base.this
#50 = NameAndType #68:#69 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#51 = NameAndType #68:#70 // append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
#52 = NameAndType #71:#72 // toString:()Ljava/lang/String;
#53 = Class #73 // java/io/PrintStream
#54 = NameAndType #74:#75 // println:(Ljava/lang/String;)V
#55 = NameAndType #27:#24 // static_string:Ljava/lang/String;
#56 = NameAndType #36:#29 // virtual_init:()V
#57 = NameAndType #37:#29 // static_init:()V
#58 = NameAndType #38:#29 // static_init2:()V
#59 = Utf8 Base virtual init
#60 = Utf8 Base static init
#61 = Utf8 Base static init2
#62 = Utf8 static base string
#63 = Utf8 Base
#64 = Utf8 java/lang/Object
#65 = Utf8 java/lang/System
#66 = Utf8 out
#67 = Utf8 Ljava/io/PrintStream;
#68 = Utf8 append
#69 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#70 = Utf8 (Ljava/lang/Object;)Ljava/lang/StringBuilder;
#71 = Utf8 toString
#72 = Utf8 ()Ljava/lang/String;
#73 = Utf8 java/io/PrintStream
#74 = Utf8 println
#75 = Utf8 (Ljava/lang/String;)V
{
java.lang.String str;
descriptor: Ljava/lang/String;
flags: int base_int;
descriptor: I
flags: static java.lang.String static_string;
descriptor: Ljava/lang/String;
flags: ACC_STATIC public Base();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String Base string
7: putfield #3 // Field str:Ljava/lang/String;
10: aload_0
11: iconst_4
12: putfield #4 // Field base_int:I
15: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
18: new #6 // class java/lang/StringBuilder
21: dup
22: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
25: ldc #8 // String Base.this
27: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: aload_0
31: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
34: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
LineNumberTable:
line 8: 0
line 5: 4
line 6: 10
line 9: 15
line 10: 40
LocalVariableTable:
Start Length Slot Name Signature
0 41 0 this LBase; protected void virtual_show();
descriptor: ()V
flags: ACC_PROTECTED
Code:
stack=2, locals=1, args_size=1
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field str:Ljava/lang/String;
7: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
13: getstatic #13 // Field static_string:Ljava/lang/String;
16: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: aload_0
20: invokevirtual #14 // Method virtual_init:()V
23: invokestatic #15 // Method static_init:()V
26: invokestatic #16 // Method static_init2:()V
29: return
LineNumberTable:
line 13: 0
line 14: 10
line 15: 19
line 17: 23
line 18: 26
line 19: 29
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 this LBase; protected void virtual_init();
descriptor: ()V
flags: ACC_PROTECTED
Code:
stack=2, locals=1, args_size=1
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #17 // String Base virtual init
5: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 22: 0
line 23: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LBase; protected static void static_init();
descriptor: ()V
flags: ACC_PROTECTED, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #18 // String Base static init
5: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 25: 0 protected static void static_init2();
descriptor: ()V
flags: ACC_PROTECTED, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #19 // String Base static init2
5: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 27: 0 static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #20 // String static base string
2: putstatic #13 // Field static_string:Ljava/lang/String;
5: return
LineNumberTable:
line 7: 0
}
SourceFile: "Base.java"
Classfile /C:/Users/chen/Desktop/Sub.class
Last modified 2016-3-31; size 1602 bytes
MD5 checksum 8b13720ed4fb15f410919e59f20b1978
Compiled from "Sub.java"
public class Sub extends Base
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #28.#52 // Base."<init>":()V
#2 = String #53 // Sub string
#3 = Fieldref #19.#54 // Sub.str:Ljava/lang/String;
#4 = Fieldref #55.#56 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Class #57 // java/lang/StringBuilder
#6 = Methodref #5.#52 // java/lang/StringBuilder."<init>":()V
#7 = String #58 // Sub.this
#8 = Methodref #5.#59 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#9 = Methodref #5.#60 // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
#10 = Methodref #5.#61 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#11 = Methodref #62.#63 // java/io/PrintStream.println:(Ljava/lang/String;)V
#12 = String #64 // Sub init
#13 = String #65 // Sub static init
#14 = Fieldref #28.#54 // Base.str:Ljava/lang/String;
#15 = Fieldref #19.#66 // Sub.static_string:Ljava/lang/String;
#16 = Fieldref #28.#66 // Base.static_string:Ljava/lang/String;
#17 = Fieldref #19.#67 // Sub.base_int:I
#18 = Methodref #62.#68 // java/io/PrintStream.println:(I)V
#19 = Class #69 // Sub
#20 = Methodref #19.#52 // Sub."<init>":()V
#21 = Methodref #19.#70 // Sub.virtual_show:()V
#22 = Methodref #19.#71 // Sub.show_field:()V
#23 = Methodref #19.#72 // Sub.static_init:()V
#24 = Methodref #19.#73 // Sub.static_init2:()V
#25 = String #74 // ---------------------------------------
#26 = Methodref #28.#70 // Base.virtual_show:()V
#27 = String #75 // static Sub string
#28 = Class #76 // Base
#29 = Utf8 str
#30 = Utf8 Ljava/lang/String;
#31 = Utf8 static_string
#32 = Utf8 <init>
#33 = Utf8 ()V
#34 = Utf8 Code
#35 = Utf8 LineNumberTable
#36 = Utf8 LocalVariableTable
#37 = Utf8 this
#38 = Utf8 LSub;
#39 = Utf8 virtual_init
#40 = Utf8 static_init
#41 = Utf8 show_field
#42 = Utf8 virtual_test
#43 = Utf8 mytest
#44 = Utf8 sub
#45 = Utf8 base
#46 = Utf8 LBase;
#47 = Utf8 RuntimeVisibleAnnotations
#48 = Utf8 Lorg/junit/Test;
#49 = Utf8 <clinit>
#50 = Utf8 SourceFile
#51 = Utf8 Sub.java
#52 = NameAndType #32:#33 // "<init>":()V
#53 = Utf8 Sub string
#54 = NameAndType #29:#30 // str:Ljava/lang/String;
#55 = Class #77 // java/lang/System
#56 = NameAndType #78:#79 // out:Ljava/io/PrintStream;
#57 = Utf8 java/lang/StringBuilder
#58 = Utf8 Sub.this
#59 = NameAndType #80:#81 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#60 = NameAndType #80:#82 // append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
#61 = NameAndType #83:#84 // toString:()Ljava/lang/String;
#62 = Class #85 // java/io/PrintStream
#63 = NameAndType #86:#87 // println:(Ljava/lang/String;)V
#64 = Utf8 Sub init
#65 = Utf8 Sub static init
#66 = NameAndType #31:#30 // static_string:Ljava/lang/String;
#67 = NameAndType #88:#89 // base_int:I
#68 = NameAndType #86:#90 // println:(I)V
#69 = Utf8 Sub
#70 = NameAndType #91:#33 // virtual_show:()V
#71 = NameAndType #41:#33 // show_field:()V
#72 = NameAndType #40:#33 // static_init:()V
#73 = NameAndType #92:#33 // static_init2:()V
#74 = Utf8 ---------------------------------------
#75 = Utf8 static Sub string
#76 = Utf8 Base
#77 = Utf8 java/lang/System
#78 = Utf8 out
#79 = Utf8 Ljava/io/PrintStream;
#80 = Utf8 append
#81 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#82 = Utf8 (Ljava/lang/Object;)Ljava/lang/StringBuilder;
#83 = Utf8 toString
#84 = Utf8 ()Ljava/lang/String;
#85 = Utf8 java/io/PrintStream
#86 = Utf8 println
#87 = Utf8 (Ljava/lang/String;)V
#88 = Utf8 base_int
#89 = Utf8 I
#90 = Utf8 (I)V
#91 = Utf8 virtual_show
#92 = Utf8 static_init2
{
java.lang.String str;
descriptor: Ljava/lang/String;
flags: static java.lang.String static_string;
descriptor: Ljava/lang/String;
flags: ACC_STATIC public Sub();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method Base."<init>":()V
4: aload_0
5: ldc #2 // String Sub string
7: putfield #3 // Field str:Ljava/lang/String;
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: new #5 // class java/lang/StringBuilder
16: dup
17: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
20: ldc #7 // String Sub.this
22: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: aload_0
26: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
29: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: return
LineNumberTable:
line 11: 0
line 7: 4
line 12: 10
line 13: 35
LocalVariableTable:
Start Length Slot Name Signature
0 36 0 this LSub; protected void virtual_init();
descriptor: ()V
flags: ACC_PROTECTED
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #12 // String Sub init
5: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 19: 0
line 20: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LSub; protected static void static_init();
descriptor: ()V
flags: ACC_PROTECTED, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String Sub static init
5: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 24: 0
line 25: 8 public void show_field();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field str:Ljava/lang/String;
7: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: getfield #14 // Field Base.str:Ljava/lang/String;
17: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
23: getstatic #15 // Field static_string:Ljava/lang/String;
26: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
32: getstatic #16 // Field Base.static_string:Ljava/lang/String;
35: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
38: return
LineNumberTable:
line 28: 0
line 29: 10
line 30: 20
line 31: 29
line 32: 38
LocalVariableTable:
Start Length Slot Name Signature
0 39 0 this LSub; public void virtual_test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #17 // Field base_int:I
7: invokevirtual #18 // Method java/io/PrintStream.println:(I)V
10: return
LineNumberTable:
line 36: 0
line 37: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this LSub; public void mytest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: new #19 // class Sub
3: dup
4: invokespecial #20 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #21 // Method virtual_show:()V
12: aload_1
13: invokevirtual #22 // Method show_field:()V
16: aload_1
17: pop
18: invokestatic #23 // Method static_init:()V
21: aload_1
22: pop
23: invokestatic #24 // Method static_init2:()V
26: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
29: ldc #25 // String ---------------------------------------
31: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: new #19 // class Sub
37: dup
38: invokespecial #20 // Method "<init>":()V
41: astore_2
42: aload_2
43: invokevirtual #26 // Method Base.virtual_show:()V
46: return
LineNumberTable:
line 41: 0
line 42: 8
line 43: 12
line 44: 16
line 45: 21
line 48: 26
line 49: 34
line 50: 42
line 51: 46
LocalVariableTable:
Start Length Slot Name Signature
0 47 0 this LSub;
8 39 1 sub LSub;
42 5 2 base LBase;
RuntimeVisibleAnnotations:
0: #48() static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #27 // String static Sub string
2: putstatic #15 // Field static_string:Ljava/lang/String;
5: return
LineNumberTable:
line 8: 0
}
SourceFile: "Sub.java"
为何JAVA虚函数(虚方法)会造成父类可以"访问"子类的假象?的更多相关文章
- C++学习 - 虚表,虚函数,虚函数表指针学习笔记
http://blog.csdn.net/alps1992/article/details/45052403 虚函数 虚函数就是用virtual来修饰的函数.虚函数是实现C++多态的基础. 虚表 每个 ...
- C++ - 类的虚函数\虚继承所占的空间
类的虚函数\虚继承所占的空间 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24236469 char占用一个字节, 但不满足4的 ...
- 虚函数&&虚继承
如果说没有虚函数的虚继承只是一个噩梦的话,那么这里就是真正的炼狱.这个C++中最复杂的继承层次在VS上的实现其实我没有完全理解,摸爬滚打了一番也算得出了微软的实现方法吧,至于一些刁钻的实现方式我也想不 ...
- C++基础 (6) 第六天 继承 虚函数 虚继承 多态 虚函数
继承是一种耦合度很强的关系 和父类代码很多都重复的 2 继承的概念 3 继承的概念和推演 语法: class 派生类:访问修饰符 基类 代码: … … 4 继承方式与访问控制权限 相对的说法: 爹派生 ...
- Java基础 - 函数与方法
java 是一门面向对象编程,其它语言中的函数也就是java中的方法 方法的基本使用方法 package com.demo7; /* * 函数/方法 * * 定义格式: * 修饰符 返回值类型 方法名 ...
- Java 中 父类变量访问子类方法 需要使用 类型转换 (instenceof)关键字 /类型判断/
通过数组元素访问方法的时候只能访问在 Animal中定义的方法,对 于 Tiger类和 Fish中定义的方法时却不能调用,例如语句 animal[2].swim();就是不正确的.当 需要访问这些 ...
- C++虚函数原理
类中的成员函数分为静态成员函数和非静态成员函数,而非静态成员函数又分为普通函数和虚函数. Q: 为什么使用虚函数 A: 使用虚函数,我们可以获得良好的可扩展性.在一个设计比较好的面向对象程序中,大多数 ...
- C++虚函数【Java有虚函数吗?】
1,简单介绍 定义在基类中的函数,子类必须对其进行覆写![必须对其进行覆写?!]——Java中的接口.Abstract方法中的抽象类也有这样的要求. C++中定义: virtual void deal ...
- JAVA – 虚函数、抽象函数、抽象类、接口
本文转载地址:http://blog.csdn.net/trojanpizza/article/details/6556604 1. Java虚函数 虚函数的存在是为了多态. C++中普通成员函数加 ...
随机推荐
- [已解决]Teamviewer VPN 连接上,但无法ping
用Teamveiwer 可以进行远程控制连接.用了VPN功能后,起先也正常.可以PING和其他网络操作. 后来忽然始终VPN连接上后,无法PING和做其他的网络操作了. 检查缘由是对方TeamView ...
- AP是什么
百度链接: AP---http://baike.baidu.com/link?url=_mC-Wkgl8j1_awpuicoZk3i4MWVcLaio1nm9XRt60F9QD4V_lJ-kE7J4C ...
- Linux & Systemd 挂载问题解决
1. 方法一: Create the following file as root: /etc/polkit-/rules.d/-nopasswd_global.rules /* Allow memb ...
- 1、linux网络服务实验 用PuTTY连接Linux
这个是大三下学期的Linux网络服务配置详解时,感觉老师上得简单,就整理下,岭南师范学院师弟妹有福,如果是蔡老师交的话,可以拿来预习,复习. 一.用PuTTY连接Linux ①.装有redhat系统的 ...
- 【小白的CFD之旅】05 补充基础
黄师姐是一个很干脆果敢的人,从她的日常装扮就能显露出来.卡帕运动装,白色运动鞋,马尾辫,这是小白对黄师姐的第一印象.“明天早上九点钟来实验室,我给你安排这阵子的任务.”黄师姐对小白说.说话语气和老蓝一 ...
- 【2016-10-28】【坚持学习】【Day15】【Oracle】【变量 定义 使用】
declare i integer ; j ; begin i :; dbms_output.put_line(j); end
- 怎样制作FL Studio步进音序器中的节奏
了解了FL Studio一些操作功能后,我们就要去用这些操作功能完成我们想要的作品.所以今天小编就来带领大家在FL Studio的步进音序器中制作出简单的节奏,与此同时大家也会了解到通道的几个基础功能 ...
- segments&cache
Segments 执行效果 命令 在 sense 里边执行 GET /abcd/_segments 前边的是索引名称,后边是请求 段信息 说明 索引是面向分片的,是由于索引是由一个或多个分片( ...
- C# 把日期字符串转换为日期类型 (MM大写为月、小写为分钟)
string dtStr; DateTime dtTime; 尝试把时间字符串转为DateTime格式 if (DateTime.TryParse(dtStr, out dtTime)) { //st ...
- selenium自动化-java-IE启动
这是一个方法,直接在main调用就可以 private static void ie() { WebDriver Driver; // ie启动成功,files是启动ie驱动 ...