final关键字
Java关键字final在不同语境意思会有稍微不同,但总的意思是“不能改变”(定义常量)。之所以要禁止改变,可能是要考虑:设计和效率。
final数据
final主要用于定义常量,常量的用处主要在两点:
- 编译期常量(compile-time constant),永远不会改变
- 运行期初始化的值,不希望改变
对于编译器常量,编译器允许将常量封装到使用计算过程中。即,会在编译期进行计算,避免运行时(run-time)不必要的开销(overhead)。Java中,这类常数必须为原始类型,用关键字final修饰。final常量在定义时必须要初始化。
无论是staic还是final的字段都只能存储一个数据,且不能改变。
当final用于对象句柄,含义有点模糊。用final修饰对象句柄时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。(可以这样理解指路的路牌上的文字是不变的,但是被指的路可能发生变化)Java对此未提供任何手段,可将一个对象直接变成一个常数
(但是,可以自己编写一个类,使其中的对象具有“常数”效果)。这一限制也适用于数组,它也属于对象。
见下例,注意一个惯例,既是static又是final的字段,其名字必须全部大写,用下划线分隔,例如VALUE_TWO
//: reusing/FinalData.java
// The effect of final on fields.
import java.util.*;
import static net.mindview.util.Print.*;
class Value {
int i; // Package access
public Value(int i) { this.i = i; } }
public class FinalData {
private static Random rand = new Random(47);
private String id;
public FinalData(String id) { this.id = id; }
// Can be compile-time constants:
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
// Typical public constant:
public static final int VALUE_THREE = 39;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
// Arrays:
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() { return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5; }
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
//! fd1.valueOne++; // Error: can’t change value
fd1.v2.i++;
// Object isn’t constant!
fd1.v1 = new Value(9);
// OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn’t constant!
//! fd1.v2 = new Value(0); // Error: Can’t
//! fd1.VAL_3 = new Value(1); // change reference //!
fd1.a = new int[3];
print(fd1);
print("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
print(fd1);
print(fd2);
}
}
/* Output:
fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18
*///:~
注意public static final int VALUE_THREE是一种常见的定义常量的方法,其中public表明可在包外使用,static强调只有一个,final指明是常量
不能由于其属性是final,就认定能在编译时知道它的值。i4和i5向大家证明了这一点。它们在运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final值设为static和非static之间的差异。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相同的
注意对于fd1和fd2来说,i4的值是唯一的,但i5的值不会由于创建了另一个FinalData对象而发生改变。那是因为它的属性是static,是在载入时初始化,而非每创建一个对象时初始化。
见上例v1到VAL_3,在主方法中,并不能认为v2是final的就不能改变它的值。因为final修饰的是句柄,即不能再将v2绑定到新的对象上,但是句柄绑定的对象是可以改变的。同样的含义也适用于数组,因为后者是另一种类型的句柄。将句柄变成final看起来似乎不如将基本数据类型变成final那么有用。
空final
空final即字段被声明为final,但未被初始化。在任何情况下,空final在使用之前都必须被初始化。然而空final更具灵活性。比如,类内部的final字段对于每个对象都不同,但又能保持自己不变(immutable)的特性
见下例:
//: reusing/BlankFinal.java
// "Blank" final fields.
class Poppet {
private int i;
Poppet(int ii) { i = ii; }
}
public class BlankFinal {
private final int i = 0; // Initialized final
private final int j; // Blank final
private final Poppet p; // Blank final reference
// Blank finals MUST be initialized in the constructor: 空final必须在构造方法中初始化
public BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet(1); // Initialize blank final reference
}
public BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet(x); // Initialize blank final reference
}
public static void main(String[] args) {
new BlankFinal();
new BlankFinal(47);
}
} ///:~
现在强制要求对final进行赋值处理——要么在定义字段时使用一个表达式,要么在每个构建器中。这样就可以确保final字段在使用前获得正确的初始化。
记得在使用前初始化
final自变量
Java 1.1允许将自变量设成final属性,方法在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,不能改变自变量句柄指向的东西。如下所示:
例如:
//: reusing/FinalArguments.java
// Using "final" with method arguments.
class Gizmo {
public void spin() {}
}
public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Illegal -- g is final
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}
// void f(final int i) { i++; } // Can’t change
// You can only read from a final primitive:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
} ///:~
注意这时仍然可以为final自变量分配一个null(空)句柄,并且编译器不会捕获它。这和对非final自变量采取的操作是一样的。
方法f()和g()展示出,基本类型的自变量为final时只能读取自变量,不可改变。
final方法
使用final方法有两个原因:
- 给方法上锁,防止继承的类对其进行更改。在设计中使用final方法是为了确保继承时方法的行为得到保留,不被重写
- 确保程序执行的效率。将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用(inline call)里。只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将参数压入堆栈;跳至方法代码并执行;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用(replace the method call with a copy of the actual code in the method body)。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。
在最新版本中,让编译器和JVM处理效率问题,只有在明确表示不像让方法被重写的时候才用final
final类
final类不能被继承,不做任何更改,也不生成任何子类
//: Jurassic.java
// Making an entire class final
class SmallBrain {}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {}
}
//! class Further extends Dinosaur {}
// error: Cannot extend final class 'Dinosaur'
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
} ///:~
注意:final类的字段可以是final也可以不是。但是因为final类不被继承,final类的方法也被隐式地为final,因为没法重写这些方法。可以给final类的方法添加final标识符,但没有什么意义。
final注意事项
慎用final,因为很难预测一个类以后会以什么样的形式再生或重复利用。