问题背景:
想了解Java类非静态成员变量以及静态成员变量的初始化过程。在代码块中使用System.out.println输出语句,输出成员变量的值,但是出现非法引用的提示。具体代码如下。
具体代码:
class A {
public A() {
System.out.println("父类A的构造方法");
System.out.println("静态成员变量 = " + staticStr + ", 非静态成员变量 = " + str);
}
{
//System.out.println(str); //错误: 非法前向引用
str = "123";
System.out.println("父类A的构造代码块0");
}
static {
staticStr = "1234";
//System.out.println(staticStr);
System.out.println("父类A的静态代码块0");
}
private static String staticStr = iniStaticStr();
static {
System.out.println(staticStr);
System.out.println("父类A的静态代码块");
}
private String str = iniStr();
{
System.out.println(str);
System.out.println("父类A的构造代码块");
}
private static String iniStaticStr() {
System.out.println("staticStr = " + staticStr);
System.out.println("静态成员变量显示初始化");
return "iniStaticStr";
}
private String iniStr() {
System.out.println("str = " + str);
System.out.println("非静态成员变量显示初始化");
return "iniStr";
}
{
//System.out.println(str);//错误: 非法前向引用
System.out.println("父类A的构造代码块1");
}
static {
System.out.println("父类A的静态代码块1");
}
}
class B extends A {
static {
System.out.println("子类B的静态代码块");
}
public B() {
System.out.println("子类B的构造方法");
}
{
System.out.println("子类B的构造代码块");
}
}
public class JavaTest1 {
public static void main(String[] args) {
new B();
}
}
输出结果:
父类A的静态代码块0
staticStr = 1234
静态成员变量显示初始化
iniStaticStr
父类A的静态代码块
父类A的静态代码块1
子类B的静态代码块
父类A的构造代码块0
str = 123
非静态成员变量显示初始化
iniStr
父类A的构造代码块
父类A的构造代码块1
父类A的构造方法
静态成员变量 = iniStaticStr, 非静态成员变量 = iniStr
子类B的构造代码块
子类B的构造方法
具体问题描述:
1、代码块与成员变量的执行顺序是怎样的?如果代码块先于成员变量执行,那此时的成员变量并没用被声明,那为什么能对成员变量进行赋值?
2、假如是成员变量先于代码块执行,为什么代码块中使用System语句输出操作会提示非法向前引用?
3、成员变量的初始化过程是否是一下过程?
1)默认初始化
2)显示初始化(包括代码块中的显示初始化)
3)构造初始化
首先,关于执行顺序问题。主要有以下几个要点:
private String str
这样的声明并不是可执行代码)关于执行顺序,你可以对照你程序的打印输出来看,应该就能明白了。
下面再来说说
非法向前引用
这个错误。前面说过,所有变量的声明都是在任意代码执行前发生的,那么按道理来说并不存在“向前引用”一说,因为任一句代码执行时变量肯定已经存在了才对。
那么这个错误究竟是怎么出现的呢?答案是:
其目的是
避免循环初始化和其他非正常的初始化
行为。所以,虽然你的代码看起来没有问题,但是却无法通过编译器的强制检查,所以报错。
那么为什么类似于
staticStr = "1234";
这样的代码可以呢?这是因为Java对其中的某些情况做了“特许”,其中有一条就是“通过简单名称引用的变量可以出现在左值位置,但不能出现在右值的位置”,所以前面的代码可以,但System.out.println(staticStr);
不行,因为这是一个右值引用。最后再简单提一下什么是循环引用,看一下下面这个例子:
如果没有前面说的强制检查,那么这两句代码就会通过编译,但是很容易就能看得出来,
i
和j
并没有被真正赋值,因为两个变量都是未初始化的(Java规定所有变量在使用之前必须被初始化),而这个就是最简单的循环引用的例子。