昨天晚上,自己在牛客网上刷了一道关于父类子类方法引用的问题,当时感觉很蒙蔽,已经上床了,就带着问题直接睡了,今早起来,把问题复现,自己又深入的了解了一下,打算记录一下这次收获的东西,希望能够帮助到大家。
问题原文
public class Test {
public static void main(String[] args) {
System.out.println(new B.getValue());
}
static class A {
protected int value;
public A (int value) {
setValue(value);
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
try {
value++;
return value;
} finally {
this.setValue(value);
System.out.println(value);
}
}
}
static class B extends A {
public B () {
super(5);
int value = getValue() - 3;
setValue(value);
}
public void setValue(int value) {
super.setValue(2 * value);
}
}
问这段代码的执行结果是啥,当时没有整明白,今天早上一边debug,一边总结自己遗漏的知识点,下面就这个问题进行一些自己的分享和理解。
在父类和子类方法引用问题的这一块,自己之前一致都在含含糊糊,不知道具体是怎样执行的,今天细细捋一遍,主要有以下几点:
1、当子类继承父类之后,子类可以通过使用super关键字,去调用父类的方法,如果不用这个关键字,则默认调用的是自己的实例方法,而对于父类来说,如果子类实现了自己定义的方法,则每次调用的时候,是调用的子类的方法,若没有实现,则调用自己的方法。
2、在构造器调用这部分,子类可以使用super关键字调用父类的构造器,要确保传入参数正确。
3、关于子类父类的各个模块的加载顺序问题:
父类静态代码块 ---- 子类静态代码块 ---- 父类非静态代码块 ---- 父类构造函数 ---- 子类非静态代码块 ---- 子类构造函数。
下面是自己的源代码分析,自己可以再使用DEBUG进行调试测试,也是根据自己的理解,有哪里不对的地方,希望能够给sulay指出来,大家一同学习。
public class VariableReferenceTest {
public static void main(String[] args) {
// 当我们在代码中执行new语句时,会默认去查找对应类的构造器
System.out.println(new SubClass().getValue());
}
static class SuperClass {
protected int value;
// 如果这个类被其子类所实现,子类采用super关键字,就能够找到这个构造器
public SuperClass(int value) {
System.out.println(value);
// 这里默认执行的是子类的方法,不回去执行父类的方法,
// 父类方法在子类进行super.methodName调用时,才能够被调用得到
setValueV1(value);
// 调用的是子类的方法
setValue(value);
System.out.println(this.value);
}
public void setValueV1(int value) {
this.value = value;
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
System.out.println("SuperClass--------------getValue()");
try {
value++;
System.out.println("value++-----------value => " + value);
// 这里会先去执行finally语句块,之后才会回来执行return语句,但是,在finally语句块中对于value值得修改,在这里没有同步???????
return value;
} finally {
this.setValue(value);
System.out.println("this.setValue----------value => " + value);
}
}
}
static class SubClass extends SuperClass {
// new之后,找到了这个类的构造器,开始执行构造器中的代码
public SubClass() {
// super关键字去找到继承父类的构造器
super(5);
// 这里调用getValue()方法进入子类的方法中,在子类的方法中,调用了父类的同名方法,
// 但是在父类中,finally代码块中对于value值的更改,不会同步到try代码块中
int value = getValue() - 3;
System.out.println(value);
setValue(value);
}
public void setValue(int value) {
super.setValue(2 * value);
}
public int getValue() {
// 死循环,一致递归调用getValue()方法
// int result = this.getValue();
System.out.println("SubClass-------------getValue()");
int result = super.getValue();
System.out.println(result);
return result;
}
}
}
虚拟机字节码文件
碰巧自己最近在学习JVM,就将上面的代码使用javap工具进行了类文件的字节码文件输出,具体执行语句是:
>javac VariableReferenceTest.java
>javap -verbose VariableReferenceTest.class
执行结束之后,你可以获得上面源代码的字节码内容,这里简要进行分析一下这个文件中的内容:
# 类文件的一些属性值,包括全路径,最近更改时间,是被哪个Java源文件编译而来的
Classfile /E:/IDEA_Storage/code/src/code/javase/classes/VariableReference.class
Last modified 2020-12-11; size 660 bytes
MD5 checksum 2d62e3581d7fe6857922e25f34f16212
Compiled from "VariableReference.java"
# 这里说明了该类的一些属性,主版本号、次版本号和标识位
public class code.javase.classes.VariableReference
minor version: 0
major version: 55
flags: ACC_PUBLIC, ACC_SUPER
# 这是该类的常量池信息
Constant pool:
#1 = Methodref #8.#22 // java/lang/Object."<init>":()V
#2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Class #25 // code/javase/classes/VariableReference$SubClass
#4 = Methodref #3.#22 // code/javase/classes/VariableReference$SubClass."<init>":()V
#5 = Methodref #3.#26 // code/javase/classes/VariableReference$SubClass.getValue:()I
#6 = Methodref #27.#28 // java/io/PrintStream.println:(I)V
#7 = Class #29 // code/javase/classes/VariableReference
#8 = Class #30 // java/lang/Object
#9 = Utf8 SubClass
#10 = Utf8 InnerClasses
#11 = Class #31 // code/javase/classes/VariableReference$SuperClass
#12 = Utf8 SuperClass
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 SourceFile
#20 = Utf8 VariableReference.java
#21 = Utf8 NestMembers
#22 = NameAndType #13:#14 // "<init>":()V
#23 = Class #32 // java/lang/System
#24 = NameAndType #33:#34 // out:Ljava/io/PrintStream;
#25 = Utf8 code/javase/classes/VariableReference$SubClass
#26 = NameAndType #35:#36 // getValue:()I
#27 = Class #37 // java/io/PrintStream
#28 = NameAndType #38:#39 // println:(I)V
#29 = Utf8 code/javase/classes/VariableReference
#30 = Utf8 java/lang/Object
#31 = Utf8 code/javase/classes/VariableReference$SuperClass
#32 = Utf8 java/lang/System
#33 = Utf8 out
#34 = Utf8 Ljava/io/PrintStream;
#35 = Utf8 getValue
#36 = Utf8 ()I
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (I)V
{
public code.javase.classes.VariableReference();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #3 // class code/javase/classes/VariableReference$SubClass
6: dup
7: invokespecial #4 // Method code/javase/classes/VariableReference$SubClass."<init>":()V
10: invokevirtual #5 // Method code/javase/classes/VariableReference$SubClass.getValue:()I
13: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
16: return
LineNumberTable:
line 5: 0
line 6: 16
}
SourceFile: "VariableReference.java"
Error: unknown attribute
NestMembers: length = 0x6
00 02 00 03 00 0B
InnerClasses:
static #9= #3 of #7; //SubClass=class code/javase/classes/VariableReference$SubClass of class code/javase/classes/VariableReference
static #12= #11 of #7; //SuperClass=class code/javase/classes/VariableReference$SuperClass of class code/javase/classes/VariableReference
这其中涉及到了虚拟机的一些操作指令,感兴趣的可以先自行阅读参考。