非静态成员变量的加载过程

代码:

class Father {
    int x = 10;

    public Father() {
        this.print();
        x = 20;
    }

    public void print() {
        System.out.println("Father.x = " + x);
    }
}

class Son extends Father {
    int x = 30;

    public Son() {
        this.print();
        x = 40;
    }

    public void print() {
        System.out.println("Son.x = " + x);
    }
}

public class Test {
    public static void main(String[] args) {
        Father f = new Son();
        System.out.println(f.x);
    }
}

结果:

Son.x = 0
Son.x = 30
20

解释:

非静态成员变量的加载过程如下:

  1. 默认初始化
  2. 显式初始化 / 代码块中初始化(这两个的执行顺序取决于代码顺序)
  3. 构造器中初始化
  4. 有了对象之后,可以使用“对象.属性”或者“对象.方法()”的方式对成员变量进行赋值

注意:显式初始化、代码块中初始化、构造器中初始化都是在init()方法中进行的

由于以上代码中不涉及静态变量/常量,静态代码块等内容,所以以下分析中不在分析静态内容;

现在我们来分析程序,程序首先进入main()方法,我们来看main()方法的字节码文件,如下所示:

 0 new #2 <com/atguigu/java/Son>
 3 dup
 4 invokespecial #3 <com/atguigu/java/Son.<init>>
 7 astore_1
 8 getstatic #4 <java/lang/System.out>
11 aload_1
12 getfield #5 <com/atguigu/java/Father.x>
15 invokevirtual #6 <java/io/PrintStream.println>
18 return

其中new #2 <com/atguigu/java/Son>代表新建了Son对象,dup代表将该对象复制一份,invokespecial #3 <com/atguigu/java/Son.<init>>代表执行Son类init()方法,我们直接看Son类init()方法的字节码文件,如下所示:

 0 aload_0
 1 invokespecial #1 <com/atguigu/java/Father.<init>>
 4 aload_0
 5 bipush 30
 7 putfield #2 <com/atguigu/java/Son.x>
10 aload_0
11 invokevirtual #3 <com/atguigu/java/Son.print>
14 aload_0
15 bipush 40
17 putfield #2 <com/atguigu/java/Son.x>
20 return

其中aload_0代表加载this到操作数栈中,invokespecial #1 <com/atguigu/java/Father.<init>>代表调用Father父类的init()方法,那我们直接看Father类init()方法的字节码文件,如下所示:

 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init>>
 4 aload_0
 5 bipush 10
 7 putfield #2 <com/atguigu/java/Father.x>
10 aload_0
11 invokevirtual #3 <com/atguigu/java/Father.print>
14 aload_0
15 bipush 20
17 putfield #2 <com/atguigu/java/Father.x>
20 return

我们来好好分析一下,aload_0代表加载this到操作数栈中,invokespecial #1 <java/lang/Object.<init>>代表调用Object父类的init()方法,这个里面没啥内容,所以就不在说了,bipush 10代表将10压入操作数栈中,putfield #2 <com/atguigu/java/Father.x>代表将10赋值给Father对象中的变量xinvokevirtual #3 <com/atguigu/java/Father.print>本来是要执行Father类中的print()方法,但是实际执行的是Son类中的print()方法,这属于方法多态性,由于还没有对Son类对象中的x变量赋值,执行print()方法输出的值是0,因此结果中 Son.x = 0,然后继续看Father类的init()方法,bipush 20代表将20压入操作数栈中,putfield #2 <com/atguigu/java/Father.x>代表将20赋值给Father对象中的变量x,所以Father类执行init()方法之后里面的变量x的值是20,这就完成了Father类的init()方法,我们回到Son类中的init()方法中,虽然上面已经写出了代码,这里在给大家写一下字节码文件,如下:

 0 aload_0
 1 invokespecial #1 <com/atguigu/java/Father.<init>>
 4 aload_0
 5 bipush 30
 7 putfield #2 <com/atguigu/java/Son.x>
10 aload_0
11 invokevirtual #3 <com/atguigu/java/Son.print>
14 aload_0
15 bipush 40
17 putfield #2 <com/atguigu/java/Son.x>
20 return

bipush 30代表将30压入操作数栈中,putfield #2 <com/atguigu/java/Son.x>代表将30赋值给Son对象中的变量xinvokevirtual #3 <com/atguigu/java/Son.print>将会调用Son类中的print()方法,此时x等于30,所以结果是 Son.x = 30bipush 40代表将40压入操作数栈中,putfield #2 <com/atguigu/java/Son.x>代表将40赋值给Son类对象中的变量x

到此为止,main()方法中的Father f = new Son();就已经执行完了,我们来看main()方法中的最后一行代码System.out.println(f.x);,由于fFather类对象,我们来看main()方法的字节码文件,字节码如下:

 0 new #2 <com/atguigu/java/Son>
 3 dup
 4 invokespecial #3 <com/atguigu/java/Son.<init>>
 7 astore_1
 8 getstatic #4 <java/lang/System.out>
11 aload_1
12 getfield #5 <com/atguigu/java/Father.x>
15 invokevirtual #6 <java/io/PrintStream.println>
18 return

astore_1代表将this存在局部变量表中下标为1的位置,getfield #5 <com/atguigu/java/Father.x>代表使用Father类对象的变量x的值,当然这个值就是20,因此结果是 20

至此,完美结束。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值