被final修饰的实例变量必须显式指定初始值,而且只能在如下3个位置指定初始值.
1.定义final实例变量时指定初始值;
2.在非静态初始化块中为final实例变量指定初始值;
3.在构造器中为final实例变量指定初始值.
对于普通实例变量,Java程序可以对它执行默认的初始化,也就是将实例变量的值指定为默认的初始值0或null;但对于final实例变量,则必须由程序显式指定初始值.
package com.lic.array;
public class FinalInstanceVaribaleTest {
public static void main(String[] args) {
FinalInstanceVaribaleTest fiv = new FinalInstanceVaribaleTest();
System.out.println(fiv.var1);
System.out.println(fiv.var2);
System.out.println(fiv.var3);
}
// 定义final实例变量时赋初始值
final int var1 = "疯狂Java讲义".length();
final int var2;
final int var3;
// 在初始化块中为var2赋初始值
{
var2 = "轻量级Java EE企业应用实战".length();
}
// 在构造器中为var3赋初始值
public FinalInstanceVaribaleTest(){
this.var3 = "疯狂XML讲义".length();
}
}
上面的程序定义了3个final修饰的实例变量.var1,var2,var3,分别在定义是时为var1赋初始值,在初始化块中为var2指定初始值,在构造器中为var3指定初始值.但是经过编译器的处理,这三种方式都会抽取到构造器中赋初始值.如果使用javap工具来分析:
Compiled from "FinalInstanceVaribaleTest.java"
public class com.lic.array.FinalInstanceVaribaleTest {final int var1;final int var2;final int var3;public static void main(java.lang.String[]);Code:0: new #1 // class com/lic/array/FinalInstance
VaribaleTest3: dup4: invokespecial #2 // Method "<init>":()V7: astore_18: getstatic #3 // Field java/lang/System.out:Ljava/
io/PrintStream;11: aload_112: getfield #4 // Field var1:I15: invokevirtual #5 // Method java/io/PrintStream.printl
n:(I)V18: getstatic #3 // Field java/lang/System.out:Ljava/
io/PrintStream;21: aload_122: getfield #6 // Field var2:I25: invokevirtual #5 // Method java/io/PrintStream.printl
n:(I)V28: getstatic #3 // Field java/lang/System.out:Ljava/
io/PrintStream;31: aload_132: getfield #7 // Field var3:I35: invokevirtual #5 // Method java/io/PrintStream.printl
n:(I)V38: returnpublic com.lic.array.FinalInstanceVaribaleTest();Code:0: aload_01: invokespecial #8 // Method java/lang/Object."<init>":
()V4: aload_05: ldc #9 // String 疯狂Java讲义7: invokevirtual #10 // Method java/lang/String.length:()
I10: putfield #4 // Field var1:I13: aload_014: ldc #11 // String 轻量级Java EE企业应用实战16: invokevirtual #10 // Method java/lang/String.length:()
I19: putfield #6 // Field var2:I22: aload_023: ldc #12 // String 疯狂XML讲义25: invokevirtual #10 // Method java/lang/String.length:()
I28: putfield #7 // Field var3:I31: return
}
从上面分析结果可以看出:final实例变量必须显式地被赋初始值,而且本质上final实例变量只能在构造器中被赋初始值.当然,就程序员变成来说,还可在定义final实例变量时指定初始值,也可以初始化块中为final实例变量指定初始值,但它们本质上是一样的.除此之外,final实例变量将不能被再次赋值.
对于final类变量而言,同样必须显式指定初始值,而且final类变量只能在2个地方指定初始值:
1.定义final类变量时指定初始值;
2.在静态初始化块中为final类变量指定初始值.
package com.lic.array;public class Demo22 {// 定义final类变量时赋初始值final static int var1 = "疯狂Java讲义".length();final static int var2;// 在静态初始化块中为var2赋初始值static{var2 = "轻量级Java EE企业应用实战".length();}public static void main(String[] args) {System.out.println(Demo22.var1);System.out.println(Demo22.var2);}}
上面程序中定义了2个final类变量var1和var2,在定义var1时为其赋初始值,在静态初始化块中为var2指定初始值.需要指出的是,经过编译器的处理,这2中方式都会被抽取到静态初始化块中赋初始值.如果使用javap工具来肥西改程序:
Compiled from "Demo22.java"
public class com.lic.array.Demo22 {static final int var1;static final int var2;public com.lic.array.Demo22();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":
()V4: returnpublic static void main(java.lang.String[]);Code:0: getstatic #2 // Field java/lang/System.out:Ljava/
io/PrintStream;3: getstatic #3 // Field var1:I6: invokevirtual #4 // Method java/io/PrintStream.printl
n:(I)V9: getstatic #2 // Field java/lang/System.out:Ljava/
io/PrintStream;12: getstatic #5 // Field var2:I15: invokevirtual #4 // Method java/io/PrintStream.printl
n:(I)V18: returnstatic {};Code:0: ldc #6 // String 疯狂Java讲义2: invokevirtual #7 // Method java/lang/String.length:()
I5: putstatic #3 // Field var1:I8: ldc #8 // String 轻量级Java EE企业应用实战10: invokevirtual #7 // Method java/lang/String.length:()
I13: putstatic #5 // Field var2:I16: return
}
上面程序为final类变量赋初始值.可以看到,var1,var2两个类变量的赋初始值过程都是放在静态初始化块内完成的.由此可见,final类变量必须显式地被赋初始值,而且本质上final实例变量只能在静态初始化块中被赋初始值.当然就程序员编程来说,还可在定义final类变量时指定初始值.也可以在静态初始化块中为final类变量指定初始值,但它们本质上是一样的.除此之外,final类变量将不能被再次赋值.
final修饰局部变量的情形则比较简单----Java本来就要求局部变量必须被显式地赋初始值,final修饰的局部变量一样需要被显式的赋初始值.与普通初始变量不同的是:final修饰的局部变量被赋初始值之后,以后再也不能对final局部变量重新赋值.
经过上面介绍,大致可以发现final修饰符的第一个简单功能:被final修饰的变量一旦被赋初始值,final变量的值以后将不会被改变.
除此之外,final修饰符还有一个功能.
package com.lic.array;public class Demo23 {public static void main(String[] args) {// 通过Price的INSTANCE访问currentPrice实例变量System.out.println(Price_23.INSTANCE.currentPrice);// 显式创建Price实例Price_23 p = new Price_23(2.8);// 通过显式创建的Price实例访问currentPrice实例变量System.out.println(p.currentPrice);}}
class Price_23{// 类成员是Price实例final static Price_23 INSTANCE = new Price_23(2.8);// 再定义一个类变量final static double initPrice = 20;// 定义该Price的currentPrice实例变量double currentPrice;public Price_23(double discount){// 根据静态变量计算实例变量currentPrice = initPrice - discount;}
}
你猜输出啥?
很明显,这是程序中增加了final修饰符的缘故.再次使用javap工具来分析下:
Compiled from "Demo23.java"
class com.lic.array.Price_23 {static final com.lic.array.Price_23 INSTANCE;static final double initPrice;double currentPrice;public com.lic.array.Price_23(double);Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: ldc2_w #3 // double 20.0d8: dload_19: dsub10: putfield #5 // Field currentPrice:D13: returnstatic {};Code:0: new #2 // class com/lic/array/Price_233: dup4: ldc2_w #6 // double 2.8d7: invokespecial #8 // Method "<init>":(D)V10: putstatic #9 // Field INSTANCE:Lcom/lic/array/Price_23;13: return
}
如果不使用final修饰程序中的initPrice类变量,看看javap的分析结果
Compiled from "Demo23.java"
class com.lic.array.Price_23 {static final com.lic.array.Price_23 INSTANCE;static double initPrice;double currentPrice;public com.lic.array.Price_23(double);Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: getstatic #2 // Field initPrice:D8: dload_19: dsub10: putfield #3 // Field currentPrice:D13: returnstatic {};Code:0: new #4 // class com/lic/array/Price_233: dup4: ldc2_w #5 // double 2.8d7: invokespecial #7 // Method "<init>":(D)V10: putstatic #8 // Field INSTANCE:Lcom/lic/array/Price_23;13: ldc2_w #9 // double 20.0d16: putstatic #2 // Field initPrice:D19: return
}
对比上面两个输出结果,不难发现当使用final修饰符变量时,如果定义该final类变量时指定了初始值,而且该初始值可以在编译时就被确定下来,系统将不会在静态初始化块中对该类变量赋初始值,而将是在类定义中直接使用该初始值代替该final变量.
对于一个使用final修饰的变量而言,如果定义该final变量时就指定初始值,而且这个初始值可以在编译时就确定下来,那么这个final变量将不再是一个变量,系统会将其当成"宏变量"处理.也就是说,所有出现该变量的地方,系统将直接把它当成对应的值处理.
对于上面的Price类而言,由于使用了final关键字修饰initPrice类变量,因此Price类的构造器中执行currentPrice = initPrice - discount; 代码时,程序直接会将initPrice替换成20.因此,执行该代码的效果相当于currentPrice = 20 - discount;.