1.实例变量和类变量的内存分配

类变量 :使用static修饰的成员变量是类变量,属于该类本身

实例变量:没有使用static修饰的成员变量是实例变量,属于该类的实例

由于同一个JVM内每个类只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间

对于实例变量而言,该类每创建一次实例,就需要为实例变量分配一块内存空间,所以,程序中有几个实例,实例变量就需要几块内存空间

2.类变量的初始化时机总是处于实例变量的初始化之前

我们先看下下面三段代码:

1)因为两个实例变量都是在创建变量的时候才开始分配空间,此时num2还没有分配,所以前向引用就会出现编译错误。



int num = num2 + 3;    //非法前向引用,会报错
int num2 = 2;



2)因为两个类变量在JVM加载类的时候分配空间,此时num2还没有分配,所以前向引用就出现编译错误。



static int num = num2 + 3;    //非法前向引用,会报错
static int num2 = 2;



3)因为类变量num2在JVM加载类的时候空间已经分配好,而num在创建实例的时候才分配空间,此时num2已经分配成功了,所以num前向引用成功。



int num = num2 + 3;    //正确使用
static int num2 = 2;



由上面三段代码块就可以验证得:类变量的初始化时机总是出于实例变量的初始化之前

3.Java对象的初始化方式及其执行顺序

Java对象的初始化方式有三种:1)构造器  2)初始化块  3)定义变量时指定初始化值

如果这三种初始化方式同时出现,也要注意,他们也有一个执行顺序的规定:

1)静态初始化块只在类第一次创建对象的时候运行一次,后面就不会再运行,而类在每次创建对象时,非静态初始化块总是会运行一次。



public class TestInit1 {
static {
        System.out.println("执行-----静态初始化代码块.");
    }
    
    {
        System.out.println("执行---非静态初始化代码块.");
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("创建第 " + i + " 个对象"); 
            new TestInit1();
            System.out.println();
        }
    }
}



运行结果:



Compiling TestInit1.java.......    
-----------OUTPUT-----------    
执行-----静态初始化代码块.
创建第 1 个对象
执行---非静态初始化代码块.

创建第 2 个对象
执行---非静态初始化代码块.



2)构造器每次创建对象时,构造器必然有执行的机会,此时,非静态初始化块必定也将获得机会并且运行在构造器之前



public class TestInit2 {

    {
        System.out.println("执行---非静态初始化代码块.");
    }
    
    public TestInit2 () {
        System.out.println("执行---构造器.");
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("创建第 " + i + " 个对象"); 
            new TestInit2();
            System.out.println();
        }
    }
}



运行结果:



Compiling TestInit2.java.......    
-----------OUTPUT-----------    
创建第 1 个对象
执行---非静态初始化代码块.
执行---构造器.

创建第 2 个对象
执行---非静态初始化代码块.
执行---构造器.



3)定义变量时指定的初始化值和初始化块中指定的初始值的执行顺序与他们在源程序中的排列顺序相同。

验证一:



public class TestInit3 {
    
    String i = "定义变量时指定的初始化值";

    {
        i = "初始化块中指定的初始值";
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("创建第 " + i + " 个对象"); 
            System.out.println(new TestInit3().i);
            System.out.println();
        }
    }
}



运行结果:



Compiling TestInit3.java.......    
-----------OUTPUT-----------    
创建第 1 个对象
初始化块中指定的初始值

创建第 2 个对象
初始化块中指定的初始值



验证二:



public class TestInit4 {

    {
        i = "初始化块中指定的初始值";
    }
    
    String i = "定义变量时指定的初始化值";

    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("创建第 " + i + " 个对象"); 
            System.out.println(new TestInit4().i);
            System.out.println();
        }
    }
}



运行结果:



Compiling TestInit4.java.......    
-----------OUTPUT-----------    
创建第 1 个对象
定义变量时指定的初始化值

创建第 2 个对象
定义变量时指定的初始化值



4.final修饰符

final变来那个在编译时就被确定下来了,相当于一个直接量。

1)final修饰的实例变量赋值时机:

  • 定义final实例变量时 指定初始值 
  • 非静态初始化模块中为final实例变量指定的初始值
  • 在构造器中为final实例变量指定初始值   

2)final修饰的类变量赋值时机: 

  • 定义final类变量时指定初始值
  • 静态初始化模块中为final实例变量指定的初始值