没有使用static修饰的成员变量是实例变量,属于该类的实例
在同一个JVM内,每个类只对应一个Class对象,但每个类可以创建多个Java对象,由于同一个JVM内每个类只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间但对于实例变量而言,该类每创建一次实例,就需要为实例变量分配一块内存空间。另外需要注意的是类变量的初始化时机总是处于实例变量的初始化时机之前的。
实例变量的初始化时机
从程序运行的角度来看,每次创建Java对象都会为实例变量分配内存空间,并对实例变量执行初始化
从语法角度来看,程序可以在3个地方对实例变量执行初始化
1.定义实例变量时指定初始值
2.非静态初始化块中对实例变量指定初始值
3.构造器中对实例变量指定初始值
public class StudyVar {
public String age;
public String name;
//非静态初始化块中对实例变量指定初始值
{
//String sex = "男",实际上会被分为两次执行
//1.String sex:创建Java对象时系统根据该语句为该对象分配内存
//2.sex = "男":这条语句将会被提取到Java类的构造器中执行
String sex = "男";
}
//定义实例变量时指定初始值
String sex = "女";
//构造器中对实例变量指定初始值
public StudyVar(String age, String name) {
this.age = age;
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "StudyVar{" +
"age='" + age + '\'' +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
public static void main(String[] args) {
StudyVar var = new StudyVar("18","lc");
System.out.println(var.toString());
}
}
执行结果为:StudyVar{age='18', name='lc', sex='女'}
其中第1,2种方式比第3种方式更早执行,但第1,2种方式的执行顺序与它们在源程序中的排列顺序相关
例如1先定义,2后定义,2会覆盖1,反之2先定义,1后定义,1会覆盖2,所以以上代码sex一直会固定为女。
注意:定义实例变量时指定的初始值,初始化块中为实例变量指定初始值的语句地位是平等的,当经过编译器处理后,它们都将被提取到构造器中,可以使用javap -c 命令来验证分析可以发现:
定义实例变量时指定的初始值,初始化块中为实例变量指定的初始值,构造器中为实例变量指定的初始值,三者的作用完全类似,都用于对实例变量指定初始值。
经过编译器处理之后,它们对应的赋值语句都被合并到构造器中。在合并过程中,定义变量语句转换得到的赋值语句,初始化块里的语句转换得到的赋值语句,总是位于构造器的所有语句之前,合并后,两种赋值语句的顺序保持它们在源代码中的顺序相关。
类变量的初始化时机
实例变量属于Java类本身,只有当程序初始化该Java类才会为该类的类变量分配内存空间,并执行初始化
从程序运行的角度来看,JVM对一个Java类只初始化一次,因此Java程序每运行一次,系统只为类变量分配一次内存空间,执行一次初始化
从语法角度来看,程序可以在2个地方对类变量执行初始化
1.定义类变量时指定初始值
2.静态初始化块中对类变量指定初始值
这两种方式的执行顺序也是与它们在源程序中排列顺序相关
类变量的初始化过程是每次运行程序,系统将会对类执行初始化,先为所有类变量分配内存空间,再按源代码中的排列顺序执行静态初始化块中所指定的初始值和定义类变量时所指定的初始值