final 关键字:
由于语境(应用环境)不同,final 关键字的含义可能会稍微产生一些差异。但它最一般的意思就是 声明“这个东西不能改变”。之所以要禁止改变,可能是考虑到两方面的因素:设计或效率。 由于这两个原因颇有些区别,所以也许会造成final 关键字的误用。
在接下去的小节里,我们将讨论final 关键字的三种应用场合:数据、方法以及类。
1.final 数据:
许多程序设计语言都有自己的办法告诉编译器某个数据是“常数”。常数主要应用于下述两个方面:
(1) 编译期常数,它永远不会改变
(2) 在运行期初始化的一个值,我们不希望它发生变化
对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算 可在编译期间提前执行,从而节省运行时的一些开销。在 Java 中,这些形式的常数必须属于基 本数据类型(Primitives),而且要用 final关键字进行表达。在对这样的一个常数进行定义的时 候,必须给出一个值。
无论static还是 final字段,都只能存储一个数据,而且不得改变。
示例:
class Value { int i = 1; } public class FinalData { // Can be compile-time constants final int i1 = 9; static final int I2 = 99; // Typical public constant: public static final int I3 = 39; // Cannot be compile-time constants: final int i4 = (int)(Math.random()*20); static final int i5 = (int)(Math.random()*20); Value v1 = new Value(); final Value v2 = new Value(); static final Value v3 = new Value(); //! final Value v4; // Pre-Java 1.1 Error:no initializer Arrays: final int[] a = { 1, 2, 3, 4, 5, 6 }; public void print(String id) { System.out.println(id + ": " + "i4 = " + i4 +", i5 = " + i5); } public static void main(String[] args) { FinalData fd1 = new FinalData(); //! fd1.i1++; // Error: can‘t change value fd1.v2.i++; // Object isn‘t constant! fd1.v1 = new Value(); // OK -- not final for(int i = 0; i < fd1.a.length; i++) fd1.a[i]++; // Object isn‘t constant! //! fd1.v2 = new Value(); // Error: Can‘t //! fd1.v3 = new Value(); // change handle //! fd1.a = new int[3]; fd1.print("fd1"); System.out.println("Creating new FinalData"); FinalData fd2 = new FinalData(); fd1.print("fd1"); fd2.print("fd2"); } }
由于i1和 I2都是具有 final属性的基本数据类型,并含有编译期的值,所以它们除了能作为编译期的常数使用外,在任何导入方式中也不会出现任何不同。I3是我们体验此类常数定义时更典型的一种方式:public表示它们可在包外使用;Static 强调它们只有一个;而 final 表明它是一个常数。注意对于含有固定初始化值(即编译期常数)的 fianl static基本数据类型,它们的名字根据规则要全部采用大写。也要注意i5 在编译期间是未知的,所以它没有大写。不能由于某样东西的属性是final,就认定它的值能在编译时期知道。i4 和i5 向大家证明了这一点。它们在运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final 值设为static 和非static 之间的差异。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相同的。这种差异可从输出结果中看出:
fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
fd2: i4 = 10, i5 = 9
注意对于fd1和 fd2自己来说,i4的值是唯一的,但 i5的值不会由于创建了另一个FinalData 对象而发生改变。那是因为它的属性是static,而且在载入时初始化,而非每创建一个对象时初始化。从v1 到v4 的变量向我们揭示出final 句柄的含义。正如大家在main()中看到的那样,并不能认为由于v2属于final,所以就不能再改变它的值。然而,我们确实不能再将v2绑定到一个新对象,因为它的属性是final。这便是final 对于一个句柄的确切含义。我们会发现同样的含义亦适用于数组,后者只不过是另一种类型的句柄而已。将句柄变成final 看起来似乎不如将基本数据类型变成 final那么有用。
2.空白final
Java 1.1 允许我们创建“空白final”,它们属于一些特殊的字段。尽管被声明成 final,但却未得到一个初始值。无论在哪种情况下,空白 final都必须在实际使用前得到正确的初始化。而且编译器会主动保证这一规定得以贯彻。然而,对于final关键字的各种应用,空白 final具有最大的灵活性。举个例子来说,位于类内部的一个final 字段现在对每个对象都可以有所不同,同时依然保持其“不变”的本质。下面列出一个例子:
//: BlankFinal.java // "Blank" final data members class Poppet { } class BlankFinal { final int i = 0; // Initialized final final int j; // Blank final final Poppet p; // Blank final handle // Blank finals MUST be initialized // in the constructor: BlankFinal() { j = 1; // Initialize blank final p = new Poppet(); } BlankFinal(int x) { j = x; // Initialize blank final p = new Poppet(); } public static void main(String[] args) { BlankFinal bf = new BlankFinal(); } }
这样就可以确保final 字段在使用前获得正确的初始化。、
3.final 自变量
Java 1.1 允许我们将自变量设成 final 属性,方法是在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,我们不能改变自变量句柄指向的东西。如下所示:
//: 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 g.spin(); } 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 时会发生什么情况:我们只能读取自变量,不可改变它。
4.final 方法
之所以要使用final 方法,可能是出于对两方面理由的考虑。第一个是为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
采用final 方法的第二个理由是程序执行的效率。将一个方法设成 final 后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final 方法调用,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受不到嵌入代码所带来的任何性能提升。
5.final 类
如果说整个类都是final(在它的定义前冠以 final关键字),就表明自己不希望从这个类继承,或者不允许其他任何人采取这种操作。
注意数据成员既可以是 final,也可以不是,取决于我们具体选择。应用于final 的规则同样适用于数据成员,无论类是否被定义成final。将类定义成 final后,结果只是禁止进行继承——没有更多的限制。然而,由于它禁止了继承,所以一个 final类中的所有方法都默认为 final。因为此时再也无法覆盖它们。
原文:http://zhenzhuangde.blog.51cto.com/10697385/1726905