转载自:https://blog.csdn.net/xdugucc/article/details/78239920

在学习《深入理解java虚拟机》一书中,关于类的初始化一章中提到了一句:静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。

并给出一个示例:

public class MyClass {
	static {
		i = 0; //给变量赋值可以正常通过
		System.out.println(i); //编译器提示非法向前引用
	}
	static int i = 1;
}

看了之后有两点疑问

1.我们知道类变量和静态语句块的初始化顺序是按照出现顺序的,在执行i = 0的时候,还未执行static int i = 1,为什么不会报变量未定义的错?

2.既然i = 0没有问题,为什么不可以访问?

带着这些问题去看了网上的一些关于向前引用的博客后,有了一些自己的理解。

看三个例子:

第一个例子:

public class MyClass {
	    void method()  
	    {  
	        System.out.println(myvar);  
	    }  
	    String myvar = "var value";  
	    
	    public static void main(String[] args) {
			new MyClass().method();
		}
}
//output:var value

在这个例子中,可以看到生成对象调用method()方法,method方法里是可以访问到myvar变量的,这是因为在new出对象时,会首先为成员变量分配存储空间并初始化。在执行method()方法时,在堆中的MyClass对象中已经有了一个名为myvar的引用并初始化指向了字符串“var char”。所以可以访问这个变量并打印出来。

第二个例子:

public class MyClass {
    int method() {return n; }  
    int m = method();  
    int n = 1;
}
//output:
//1
//0

这个例子,打印n和m的话依次输出1和0。现在分析对象创建的过程来解释这样的结果。new MyClass()执行后,首先会在堆中划出一片区域存储对象,并且为成员变量赋默认的初始值(类似于类加载过程中的准备阶段为静态变量非final变量赋默认值),然后再按照成员变量的出现顺序进行初始化(这个时候可以运行java代码),等所有成员变量初始化完成之后调用构造方法完成对象的创建。

现在根据以上一段的分析,m和n一开始都是0,然后开始初始化,执行m=method(); 这个时候n还未初始化还是默认值0,所以返回值赋给m,m是0。再来初始化n,执行int n = 1后,n由0变成1。所以对象创建完成之后m=0,n=1。输出结果也就不难解释了。

第三个例子:

public class MyClass {
 
    int m = n;   //非法向前引用
    {
    	n = 0;
    	System.out.println(n); //非法向前引用
    }
    int n = 1;   
}

在编译这段代码时,编译期无法通过,提示非法向前引用。似乎这个例子中,int m=n和上个例子中int m = method(); method() {return n;}n的效果是一样的。其实,如果编译能够通过,确实效果是一样的。按照我们的分析,int m = n和代码块中println(n)是没有问题的,可以给m赋还未初始化n的默认值0以及打印n的默认值0,但是java却不允许我们这样做。仔细想想就会明白,这是java出于安全性考虑的:如果字段会进行初始化,一定要防止在初始化前程序中访问默认值(可以赋值但不可访问),这会导致很多意想不到的麻烦

在构造器多态行为一文http://blog.csdn.net/xdugucc/article/details/78220518中,我们已经见过这种做法带来的问题。所以编译期会对变量还未完全初始化却在前面被引用的现象进行检查并规避,但是如果像第二个例子中的int m = method(); method() {return n;}的现象,编译器却无能为力,因为在编译期它无法知道是什么样的值赋值给了m。所以编译器并不会报错,但是我们要避免这样的现象发生。

现在再来看本文一开始的例子就很清晰了,虽然举的三个例子是基于对象的创建过程,而一开始的例子是基于类加载的过程,但是意思确实相通的。就是对于有初始值的变量,要避免在初始化之前访问变量的默认值。但是赋值确是可以的,因为最终变量还是会进行初始化。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐