Concept
许多编译语言都有某种方法,来向编译器告知一块数据是恒定不变的。有时数据的恒定不变很用,比如 1. 一个永不改变的编译时常量 2. 一个在运行时被初始化的值,而你不希望它被改变。
对于编译器常量,编译器可以将该常量值代入任何可能用到它的计算式中,也就是说,可以在编译时执行计算式,这减轻了一些运行时的负担。在Java中,这类变量必须是基本数据类型,并且以关键字final表示。在对这个常量进行定义的时候,必须对其进行赋值。
final 数据
- 对于基本类型,final使数值恒定不变;对于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而,对象其本身却是可以被修改的,Java并未提供使任何对象恒定不变的途径(但可以自己编写类以取得使对象恒定不变的效果)。
- 一个既是static又是final的域只占据一段不能改变的存储空间。根据惯例,既是static又是final的域将用大写表示,并使用下划线分割各个单词。
Example
class Value{
int i;
public Value(int i ){ this.i = i;}
}
class Data{
private static Random rand = new Random(47);
private String id;
public Data(String id) { this.id = id;}
private final int v1 = 9;
private final Value v2 = new Value(22);
private Value v3 = new Value(11);
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private final int[] a = {1, 2, 3, 4, 5, 6};
public static void main(String args[]){
Data f1 = new Data("f1");//创建一个Data对象
//!f1.v1++ 这里错误是因为v1被声明为final 无法改变其值
f1.v2.i++;//v2虽然被声明了为final v2的成员属性依然可以被改变
//!f1.v2 = new Value(0); v2被声明了final 所以v2这个引用不可以被改变
f1.v3 = new Value(9);// v3没有被声明为final 可以改变
for(int i = 0; i < f1.a.length;i++){
f1.a[i]++;
}//a被声明了final 但是数组里面的元素依然可以被改变
//!f1.a = new int[3]; a作为引用却不可以被改变
Data f2 = new Data("f2");//创建一个新的Data对象
System.out.println(f1.i4);//15
System.out.println(f2.i4);//13
System.out.println(f1.INT_5);//18
System.out.println(f1.INT_5);//18
}
}
注意在f1和f2中,i4的值都是每个对象唯一的,但是INT_5的值不可以通过创建第二个Data对象而加以改变。这是因为它是static的,在装载时已被初始化,而不是每次创建新对象时都初始化。
我们也可以看出final声明一个引用以后,并不是认为无法改变它的值。而是意味着无法将这个引用指向另一个新的对象。这对数组具有同样的意义,因为数组只不过是另一种引用。
空白 final
- Java允许生成“空白final”,所谓空白final是指被声明为final但又未给定初值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。但是,空白final在关键字final的使用上提供了更大的灵活性,为此,一个类中的final域就可以做到根据对象而有所不同,却又保持其恒定不变的特性。
- 必须在域的定义处或者每个构造器中用表达式对final进行赋值,这是因为final域在使用前必须被初始化。
Example
class Poppet{
private int i ;
Poppet(int ii){ i = ii;}
}
class BlankFinal {
private final int i = 0;
private final int j;
private final Poppet p;
public BlankFinal(){
j = 1;
p = new Poppet(1);
}
public BlankFinal(int x) {
j = x;
p = new Poppet(x);
}
}
final 参数
- Java 允许在参数列表中以声明的方式将参数指定为final。这一特性主要用来向匿名内部类传递数据。
- 当参数为基本类型变量时,变量的值不可改变;当参数为引用类型时,参数的引用地址不可改变。
final 方法
- 使用final方法的原因是把方法锁定,以防任何继承类修改它的含义。这是出于设计的考虑:想要确保在继承中使方法行为保持不变,并且不会被覆盖。
- final修饰方法表示不允许被子类重写,但是可以被子类继承,不能修饰构造方法。
- 类中所有的private方法都隐式地指定为是final的。由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加fianl修饰词,但这并不能给该方法增加任何额外的意义。
final 类
- 当将某个类的整体定义为final时(通过将关键字final置于它的定义之前),就表明了你不打算继承该类,而且也不允许别人这样做。换句话说,出于某种考虑,你对该类的设计永不需要做任何改动,或者出于安全的考虑,你不希望它有子类。
- 由于final类禁止继承,所以final类中所有的方法都隐式指定为是final的,因为无法覆盖它们。