总结关键字final的用法。
author: ZJ 07-3-16
在Java中声明属性、方法和类时,可使用关键字final来修饰。final变量即为常量,只能赋值一次;final方法不能被子类重写;final类不能被继承。
1.final成员
声明 final 字段有助于优化器作出更好的优化决定,因为如果编译器知道字段的值不会更改,那么它能安全地在寄存器中高速缓存该值。final 字段还通过让编译器强制该字段为只读来提供额外的安全级别。
1.1关于final成员赋值
1)在java中,普通变量可默认初始化。但是final类型的变量必须显式地初始化。
2)final 成员能且只能被初始化一次。
3)final成员必须在声明时(在final变量定义时直接给其赋值)或者在构造函数中被初始化,而不能在其它的地方被初始化。
示例1 Bat.java
|
public class Bat {
final double PI = 3.14; // 在定义时赋值
final int i; // 因为要在构造函数中进行初始化,所以此处便不可再赋值
final List<Bat> list; // 因为要在构造函数中进行初始化,所以此处便不可再赋值
Bat() {
i = 100;
list = new LinkedList<Bat>();
}
Bat(int ii, List<Bat> l) {
i = ii;
list = l;
}
public static void main(String[] args) {
Bat b = new Bat();
b.list.add(new Bat());
// b.i=25;
// b.list=new ArrayList<Bat>();
System.out.println("I=" + b.i + " List Type:" + b.list.getClass());
b = new Bat(23, new ArrayList<Bat>());
b.list.add(new Bat());
System.out.println("I=" + b.i + " List Type:" + b.list.getClass());
}
} |
结果:
I=100 List Type:class java.util.LinkedList
I=23 List Type:class java.util.ArrayList
在main方法中有两行语句注释掉了,如果你去掉注释,程序便无法通过编译,这便是说,不论是i的值或是list的类型,一旦初始化,确实无法再更改。然而b可以通过重新初始化来指定i的值或list的类型。
1.2 final引用字段的无效初始化
要正确使用final字段有点麻烦,对于其构造子能抛出异常的对象引用来说尤其如此。因为 final 字段在每个构造器中必须只初始化一次,如果 final 对象引用的构造器可能抛出异常,编译器可能会报错,说该字段没有被初始化。编译器一般比较智能化,足以发现在两个互斥代码分支(比如,if...else 块)的每个分支中的初始化恰好只进行了一次,但是它对 try...catch 块通常不会如此“宽容”。
下面这段代码通常会出现问题。
|
class Thingie {
public static Thingie getDefaultThingie() {
return new Thingie();
}
}
public class Foo {
private final Thingie thingie;
public Foo() {
try {
thingie = new Thingie();
} catch (Exception e) {
thingie = Thingie.getDefaultThingie();//Error:The final field thingie may already have been assigned
}
}
} |
你可以这样修改。
|
public class Foo {
private final Thingie thingie;
public Foo() {
Thingie tempThingie;
try {
tempThingie = new Thingie();
} catch (Exception e) {
tempThingie = Thingie.getDefaultThingie();
}
thingie = tempThingie;
}
} |
1.3关于final成员使用
当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。然而,对象其本身却是可以被修改的,Java并未提供使任何对象恒定不变的途径。这一限制同样适合数组,它也是对象。
示例2
|
private final int VAL_ONE=9;
private static final int VAL_TWO=99;
public static final int VAL_THREE=999; |
由于VAL_ONE 和VAL_TOW 是带有编译期数值的final 原始类型,所以它们二者均可以用作编译期常量,并且没有重大区别。VAL_THREE是一种更加典型的对常量进行定义的方式:定义为 public,则可以被用于包之外;定义为 static 来强调只有一份;定义为 final 来说明它是一个常量。
final标记的变量即成为常量,但这个“常量”也只能在这个类的内部使用,不能在类的外部直接使用。但是当我们用public static final 共同标记常量时,这个常量就成为全局的常量(一个既是static又是final的字段只占据一段不能改变的存储空间)。而且这样定义的常量只能在定义时赋值,其他地方都不行。
示例3
|
class Value {
int i;
public Value(int i) {
this.i = i;
}
}
public class FinalData {
private static Random rand = new Random();
private String id;
public FinalData(String id) {
this.id = id;
}
private final int i4 = rand.nextInt(20);
static final int i5 = rand.nextInt(20);
public String toString() {
return id + ":" + "i4:" + i4 + ", i5=" + i5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
System.out.println(fd1);
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
System.out.println(fd1);
System.out.println(fd2);
}
} |
结果
fd1:i4:6, i5=3
Creating new FinalData
fd1:i4:6, i5=3
fd2:i4:17, i5=3
示例部分展示了将final 数值定义为static(i5) 和非static(i4) 的区别。此区别只有在数值在运行期内被初始化时才会显现,这是因为编译器对编译期数值一视同仁。(并且它们可能因优化而消失。)当你运行程序时,就会看到这个区别。请注意,在fd1 和fd2 中, i5 的值是不可以通过创建第二个FinalData 对象而加以改变的。这是因为它是 static,在装载时已被初始化,而不是每次创建新对象时都初始化。
示例4
|
class Value {
int i;
public Value(int i) {
this.i = i;
}
}
public class … {
private Value v1=new Value(11);
private final Value v2=new Value(22);
private static final Value v3=new Value(33);
…
}
public static void main(String[] args) {
…
fd1.v2.i++;// OK--Object isn't constant!
fd1.v1=new Value(9);//OK--not final
fd1.v2=new Value(0);//Error:Can't change reference
fd1.v3=new Value(1);//Error:Can't change reference
…
} |
从v1 到v3 的变量说明了final 引用的意义。正如你在main( )中所看到的,不能因为v2 是final 的,就认为你无法改变它的值。由于它是一个引用,final 意味着你无法将v2 再次指向另一个新的对象。
示例5
|
public class … {
private final int[] a={1,2,3,4,5,6};
…
}
public static void main(String[] args) {
…
for(int i=0;i<fd1.a.length;i++)
fd1.a[i]++;// OK--Object isn't constant!
fd1.a=new int[3];//Error:Can't change reference …
} |
对数组具有同样的意义(可以改变它的值,但不能指向一个新的对象),数组是另一种引用。
1.4解决final数组的局限性
尽管数组引用能被声明成 final,但是该数组的元素却不能。这意味着暴露 public final 数组字段的或者通过它们的方法将引用返回给这些字段的类都不是不可改变的。
|
// Not immutable -- the states array could be modified by a malicious
// callerpublic
class DangerousStates {
private final String[] states = new String[] { "Alabama", "Alaska", "ect" };
public String[] getStates() {
return |