成员变量赋值问题【向前引用】

本文通过几个具体的Java代码示例,探讨了Java中变量初始化的顺序及其背后的原理,包括构造代码块、成员变量赋值及构造函数之间的执行顺序。

今天,同学问了我一个问题,代码如下


public class Test10 {

    //这个小程序 为啥不报错,应该是先执行a=10这个程序块,但是我们没有设定a的类型啊
    {
        a = 10;
    }

    int a = 20;


    public static void main(String[] args) {
        Test10 t = new Test10();
        System.out.println("a的数值为:" + t.a);
    }
}

  • 开始的时候,我看到这个代码有点懵了(卧槽,谁会这样用啊),于是我就在想:为什么不报错,是不是JVM会把代码全都编译一次,执行到构造代码块的时候,发现后面有对a变量进行定义,所以没有报错。于是就上网查资料去了,后来发现java支持向前引用
  • 接着,我发现输出的值是20(卧槽,怎么是20???,不应该是a=10构造代码块覆盖了在定义成员变量时a=20吗)
  • 于是乎,我又作死地加入了一个构造函数进去,看看执行顺序到底是怎么样的

public class Test10 {


    public Test10() {

        a=30;
    }

    //这个小程序 为啥不报错,应该是先执行a=10这个程序块,但是我们没有设定a的类型啊
    {
        a = 10;
    }

    int a = 20;


    public static void main(String[] args) {
        Test10 t = new Test10();
        System.out.println("a的数值为:" + t.a);
    }
}

  • 因为java支持向前引用,构造函数的a那当然不会报错啦。我再次执行该测试的时候,发现a的值为30,此时又懵逼了。按我当时理解是这样的(既然上面输出的是20,那么我加入构造方法,构造方法在构造代码块后执行,同样会被a=20覆盖掉【当时候脑袋短路了,忘记构造方法会覆盖掉成员变量的值】
  • 最后我在知乎RednaxelaFX—-R大中一个回答中似乎找到了答案。截图如下:

  • 简要提炼下语句:对JVM来说所有实例初始化动作都要收集到“特殊的实例初始化方法”(名为“init”,内容对应所有实例初始化器+构造器)里,按代码顺序把实例初始化动作(包括实例字段初始化与匿名的实例初始化器)收集起来,然后是构造器自身的内容
  • 就是说构造代码块和成员变量的赋值顺序是依照代码的顺序执行的
  • 剔除构造方法,将构造代码块和声明成员变量位置交换一下,看看结果

public class Test10 {


    int a = 20;

    {
        a = 10;
    }


    public static void main(String[] args) {
        Test10 t = new Test10();
        System.out.println("a的数值为:" + t.a);
    }
}

  • 输出的结果是10。

  • ps:如果我有理解错误的地方,请留言,谢谢!

参考资料:

向前引用:http://www.cnblogs.com/nokiaguy/p/3156357.html

执行顺序RednaxelaFX的回答:https://www.zhihu.com/question/36643366?q=java%E6%88%90%E5%91%98%E5%8F%98%E9%87%8F%E5%88%B0%E5%BA%95%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E8%A2%AB%E8%B5%8B%E5%80%BC%EF%BC%9F

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>
### C语言中指针与数组赋值的相关规则和方法 #### 指针数组的概念及其初始化 在C语言里,指针数组是指每个元素均为指针类型的数组。例如有三个整型变量`a`, `b`, 和`c`被声明并赋予初始值1, 2, 和3;接着创建了一个名为`d`的指针数组来存储这些变量的地址[^1]。 ```c int a = 1, b = 2, c = 3; int *d[5] = {&a, &b, &c}; ``` 这里`d`是一个含有五个元素的指针数组,其中前三个元素分别保存着`a`、`b`以及`c`的地址。通过这种方式可以直接访问原始数据的位置,比如`*d[1]`就等于`b`的值。 #### 使用指针遍历数组的方法 当涉及到利用指针去迭代处理一个已知大小的整形数组时,可以通过不同的方式实现: - **直接索引**:最直观的方式是像常规数组那样按位置读取元素。 ```c for(int i = 0; i < 5; i++) { printf("%d\n", a[i]); } ``` - **基于偏移量解引用**:另一种常见做法是以基址加上适当位移的方式来获取特定项的内容。 ```c for(int i = 0; i < 5; i++) { printf("%d\n", *(a + i)); } ``` - **移动指针本身**:还可以让指针逐步前进至下一个目标位置直至结束条件满足为止。 ```c for(p = a; p < (a + 5); p++) { printf("%d\n", *p); } ``` 上述三种循环结构均能有效地打印出给定数组中的全部成员[^2]。 #### 关于指针运算符的行为特性 对于表达式`*p++`而言,在执行过程中会遵循如下逻辑顺序:首先返回当前所指向对象的副本(即解除一次间接寻址),之后再更新该指针使其向前推进一位。因此如果存在这样的语句序列,则第一次调用将会显示第一个元素的数值而第二次则是第二个元素的数据[^3]。 ```c printf("%d\n", *p++); // 输出的是*p原本指向的那个数,随后p自增 printf("%d\n", *p); // 此刻由于上一步已经进行了增量操作,故此处输出的就是下一项 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值