被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
VaribaleTest
3: dup
4: invokespecial #2 // Method "<init>":()V
7: astore_1
8: getstatic #3 // Field java/lang/System.out:Ljava/
io/PrintStream;
11: aload_1
12: getfield #4 // Field var1:I
15: invokevirtual #5 // Method java/io/PrintStream.printl
n:(I)V
18: getstatic #3 // Field java/lang/System.out:Ljava/
io/PrintStream;
21: aload_1
22: getfield #6 // Field var2:I
25: invokevirtual #5 // Method java/io/PrintStream.printl
n:(I)V
28: getstatic #3 // Field java/lang/System.out:Ljava/
io/PrintStream;
31: aload_1
32: getfield #7 // Field var3:I
35: invokevirtual #5 // Method java/io/PrintStream.printl
n:(I)V
38: return
public com.lic.array.FinalInstanceVaribaleTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":
()V
4: aload_0
5: ldc #9 // String 疯狂Java讲义
7: invokevirtual #10 // Method java/lang/String.length:()
I
10: putfield #4 // Field var1:I
13: aload_0
14: ldc #11 // String 轻量级Java EE企业应用实战
16: invokevirtual #10 // Method java/lang/String.length:()
I
19: putfield #6 // Field var2:I
22: aload_0
23: ldc #12 // String 疯狂XML讲义
25: invokevirtual #10 // Method java/lang/String.length:()
I
28: putfield #7 // Field var3:I
31: 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_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/
io/PrintStream;
3: getstatic #3 // Field var1:I
6: invokevirtual #4 // Method java/io/PrintStream.printl
n:(I)V
9: getstatic #2 // Field java/lang/System.out:Ljava/
io/PrintStream;
12: getstatic #5 // Field var2:I
15: invokevirtual #4 // Method java/io/PrintStream.printl
n:(I)V
18: return
static {};
Code:
0: ldc #6 // String 疯狂Java讲义
2: invokevirtual #7 // Method java/lang/String.length:()
I
5: putstatic #3 // Field var1:I
8: ldc #8 // String 轻量级Java EE企业应用实战
10: invokevirtual #7 // Method java/lang/String.length:()
I
13: putstatic #5 // Field var2:I
16: 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_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc2_w #3 // double 20.0d
8: dload_1
9: dsub
10: putfield #5 // Field currentPrice:D
13: return
static {};
Code:
0: new #2 // class com/lic/array/Price_23
3: dup
4: ldc2_w #6 // double 2.8d
7: invokespecial #8 // Method "<init>":(D)V
10: 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_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: getstatic #2 // Field initPrice:D
8: dload_1
9: dsub
10: putfield #3 // Field currentPrice:D
13: return
static {};
Code:
0: new #4 // class com/lic/array/Price_23
3: dup
4: ldc2_w #5 // double 2.8d
7: invokespecial #7 // Method "<init>":(D)V
10: putstatic #8 // Field INSTANCE:Lcom/lic/array/Price_23;
13: ldc2_w #9 // double 20.0d
16: putstatic #2 // Field initPrice:D
19: return
}
对比上面两个输出结果,不难发现当使用final修饰符变量时,如果定义该final类变量时指定了初始值,而且该初始值可以在编译时就被确定下来,系统将不会在静态初始化块中对该类变量赋初始值,而将是在类定义中直接使用该初始值代替该final变量.
对于一个使用final修饰的变量而言,如果定义该final变量时就指定初始值,而且这个初始值可以在编译时就确定下来,那么这个final变量将不再是一个变量,系统会将其当成"宏变量"处理.也就是说,所有出现该变量的地方,系统将直接把它当成对应的值处理.
对于上面的Price类而言,由于使用了final关键字修饰initPrice类变量,因此Price类的构造器中执行currentPrice = initPrice - discount; 代码时,程序直接会将initPrice替换成20.因此,执行该代码的效果相当于currentPrice = 20 - discount;.