源代码变成可运行程序,需要经过编译——>加载——>运行几个阶段。
final修饰的变量必须显性进行初始化。初始化有三种方式:
(1)直接初始化
(2)动态代码块
(3)构造函数
如果一个变量既被final修饰又被static修饰,那么这个变量一定要被初始化(满足final特性),另外要么直接初始化要么在静态
代码块中进行初始化(满足static特性)。
final修饰的变量的初始化先于static修饰的变量,static修饰的变量先于普通变量的初始化。
如果是final级别的,那么虽然也是根据代码次序的前后顺序进行初始化,但是它是在编译阶段进行宏替换,即在编译阶段就知道了值,
因此在加载运行阶段对这一先后顺序完全感知不到。
但是如果是static级别的,代码的先后顺序会造成程序运行结果的不同。因为它们都是在加载进行的时候进行初始化的。
归结如下:
(1)final修饰的变量在编译阶段完成
(2)static修饰的变量在一个静态代码块中进行初始化
(3)普通变量在构造函数中进行初始化
1 class Price { 2 final static Price INSTANCE=new Price(2.8); 3 final static double initPrice=20; 4 double currentPrice; 5 public Price(double discount) { 6 currentPrice=initPrice-discount; 7 } 8 } 9 10 public class Test { 11 public static void main(String[]args) { 12 System.out.println(Price.INSTANCE.currentPrice); 13 } 14 }
运行结果:17.2
1 class Price { 2 static Price INSTANCE=new Price(2.8); 3 static double initPrice=20; 4 double currentPrice; 5 public Price(double discount) { 6 currentPrice=initPrice-discount; 7 } 8 } 9 10 public class Test { 11 public static void main(String[]args) { 12 System.out.println(Price.INSTANCE.currentPrice); 13 } 14 }
运行结果:-2.8
1 class Price { 2 static double initPrice=20; 3 static Price INSTANCE=new Price(2.8); 4 double currentPrice; 5 public Price(double discount) { 6 currentPrice=initPrice-discount; 7 } 8 } 9 10 public class Test { 11 public static void main(String[]args) { 12 System.out.println(Price.INSTANCE.currentPrice); 13 } 14 }
运行结果:17.2
1 class Price { 2 Price INSTANCE=new Price(2.8); 3 public Price(double discount) { 4 5 } 6 } 7 8 public class Test { 9 public static void main(String[]args) { 10 new Price(29); 11 } 12 }
运行结果:Exception in thread "main" java.lang.*Error
程序分析对比:
这几个程序,其实就是要得出currentPrice变量的结果,它是一个普通变量,因此它是在构造函数当中进行初始化的。要得出currentPrice得先有INSTANCE,
INSTANCE就是一个Price的实例,要有INSTANCE就得先调用构造函数。构造函数就会去看此时的initPrice和discount变量的初始化值。
对于程序(1)因为变量都是final修饰的,因此代码的前后顺序对于加载运行时的代码来说初始化顺序都是一致的。
对于程序(2)因为变量是由static修饰,又因为变量INSTANCE的代码顺序先于initPrice,因此INSTANCE的初始先于initPrice的初始化,这是运行时代码可见的。
于是当程序先初始化INSTANCE,INSTANCE的初始化需要initPrice和discount两个变量,而此时initPirce变量还没有显性的被代码初始化,而只是系统初始化,因此
此时initPrice此时的值为0。所以结果为-2.8。
对于程序(3)变量同样是由static修饰的,变量initPrice的代码顺序先于变量INSTANCE,因此initPrice的初始化先于INSTANCE的初始化。于是当程序需要得到currentPrice的值时,
需要调用INSTANCE变量,而INSTANCE的变量这时会去查看initPrice的值,此时它已经初始化,因此结果为17.2。
对于程序(4)当我们new实例时,就是调用构造函数给实例变量进行初始化。而普通变量的初始化时将其拿到构造函数当中来实现的。相当于上面的代码变成了:
1 class Price { 2 public Price(double discount) { 3 Price INSTANCE=new Price(2.8); 4 } 5 }
外面调用构造函数,而构造函数又调用自身。因此一直在调用构造函数,一直入栈。造成栈溢出。
上面的程序产生的结果发生在代码加载阶段,因此才会出现static代码先后顺序的问题;如果代码是由实例调用的那么,因代码顺序不同而导致的初始化顺序不一致问题会不可见。即对于
实例调用阶段的代码初始化是一致的。正如,final对于加载阶段是一致的一样。