[JAVA]类的加载,初始化顺序和向前引用

所谓向前引用,即在定义某个变量前使用它

在c中向前引用一定是错误的,而在Java中,向前引用在某些时候是可行的

为什么java可以实现向前引用(这里只是讨论可行性的原理,实际上java对向前引用做了许多限制,所以这里许多看似无错的案例实际上是不被允许的))

为一个变量分配好空间,填入了默认值,之后无论是读取它还是赋值都是可行的

当在java程序中创建一个对象实例时:
 1.先查看类有没有被加载,没有的话首先完成加载类
   类的加载会做如下的事情:
        加载class文件,给所有static属性分配空间,将这片存储空间清零,这等同于设置了默认值(0,null)
        再按照源文件中书写顺序进行指定初始化
 2.当类的加载完成后
   给所有非static属性分配空间,设置默认值
   按照书写顺序进行指定初始化
   进行构造器初始化
   创建过程结束

对于静态属性
   static int b = a;
   static Integer  a = 1;
在执行这两条语句的赋值动作之前,a、b被加载进内存,拥有了空间和默认值
所以按理来说,b在a的定义语句之前使用它是没有问题的
对于非静态属性,它的加载在静态属性之后,所以不能用非静态属性去给静态属性赋值(即使甚至没有使用向前引用):
        int b = 1;
        static int a = b;
而非静态属性的执行初始化就宽松多了,可以使用非静态的其他属性,也可以使用静态属性,因为这时所有属性都已经被加载了:
            int a = b;
            int b = 1;
            或
            int a = b;
            static int b = 1;
按照原理,这两种都不会有什么问题

注:静态属性和静态代码块的地位是一样的,非静态属性和非静态代码块的地位是一样的

*java对向前引用的限制

在指定初始化时,若使用了别的属性值,如语句int a = b;
   因为书写顺序的原因,存在两种可能
   (1)b未完成自己的指定初始化,这时b的值为默认值
   (2)b已经完成了自己的指定初始化,这时b的值为你设定的值

在定义语句前使用一个属性,往往并非程序员特意设计,而是因为疏漏
如上述的例子
            int a = b;//a == 0
            int b = 1;//b == 1;
乍一看这个代码的目的是让a和b的值都为1
但实际上,执行到a的指定初始化时,b的指定初始化尚未执行,所以此时的b仍为默认值(0)

为了避免类似的问题,java对向前引用做了一些限制
允许在定义一个字段前为它赋值,不允许在定义一个字段前读取它或者修改它
这里有一个小陷阱
    {
       a = 1;//right
       int b = a;//error
    }
    int a;
即使对a提前完成了指定初始化,也不可以在定义它的语句之前使用它

不过有一些方法可以绕过上面的限制,在属性的定义语句之前进行不止赋值的操作
实际上,当同时满足下面4个条件,编译器就会判断当前的向前引用是非法的(也就是说打破任意一项就可以做到向前引用):
1. 该属性若是静态的,出现在了静态属性的初始化语句或者静态的初始化块中
   该属性若是非静态的,出现在了非静态的初始化语句或者非静态的初始化块中
2. 该属性不在赋值语句的左边
3. 直接使用变量名来访问属性
4.以上所有发生在直接包含该属性的类或者接口中

实例

class UseBeforeDeclaration {
    static {
        x = 100;
        // ok - assignment
        int y = x + 1;
        // error - read before declaration
        int v = x = 3;
        // ok - x at left hand side of assignment
        int z = UseBeforeDeclaration.x * 2;
        // ok - not accessed via simple name

        Object o = new Object() {
            void foo() { x++; }
            // ok - occurs in a different class
            { x++; }
            // ok - occurs in a different class
        };
    }

    {
        j = 200;
        // ok - assignment
        j = j + 1;
        // error - right hand side reads before declaration
        int k = j = j + 1;
        // error - illegal forward reference to j
        int n = j = 300;
        // ok - j at left hand side of assignment
        int h = j++;
        // error - read before declaration
        int l = this.j * 3;
        // ok - not accessed via simple name

        Object o = new Object() {
            void foo(){ j++; }
            // ok - occurs in a different class
            { j = j + 1; }
            // ok - occurs in a different class
        };
    }

    int w = x = 3;
    // ok - x at left hand side of assignment
    int p = x;
    // ok - instance initializers may access static fields
    //注:规则1目的在于:
    // 限制静态属性初始化使用未定义的静态属性
    // ,限制非静态属性初始化使用未定义的非静态属性
    //但正如前面分析的,非静态属性可以任意使用静态属性,所以这里没问题
    //另外,想要破坏规则1,还可以把赋值语句放进构造器或者函数里,满足了不在初始化语句或者不在初始化代码块的条件,如:
    int b;
    int a = -1;
    UseBeforeDeclaration() {
        b = a;//在构造器中初始化
    }

    static int u =
            (new Object() { int bar() { return x; } }).bar();
    // ok - occurs in a different class

    static int x;

    int m = j = 4;
    // ok - j at left hand side of assignment
    int o =
            (new Object() { int bar() { return j; } }).bar();
    // ok - occurs in a different class
    int j;
}

public class Test {
  int b = this.a;//不是通过变量名直接访问,通过方法访问也可以
  // int b = getA();
  int a = -1;
}

* 以上所有讨论都是限于类的成员变量,并不适用在方法中的本地变量,本地变量无法做到向前引用
  对于局部变量,既不能在定义它前使用它,也不能在定义它前给它赋值
  本地变量并不会有默认值,当你使用一个未经指定初始化的本地变量,永远都会直接报错  

为什么Java不像对待成员属性那样对待本地变量呢,给它赋一个默认值呢?
对于一个成员属性,它可能会在声明处完成指定初始化,也可能会在某个代码块或者函数内部完成,也可能在构造器里完成
所以在使用一个成员属性的时候,编译器很难确定它是否已经完成了指定初始化,这时候一个适当的默认值就很有必要了 

对于生存期和作用域局限于方法块中的普通本地变量,它的初始化语句显然就在这个块里,这时候很容易判断出这个变量是否完成了初始化,不赋给默认值,而是强制程序员在完成初始化后再使用变量,可以避免很多错误

参考文档

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值