Java 中的 final
关键字可用于不同的上下文中。用于修饰一个类或一个变量或一个方法,而且仅仅只能修饰它们。final
关键字不是一个 访问修饰符,因此可以和访问修饰符一起使用。
下图,列出了 final
修饰符在修饰类、方法和变量时的不同作用。
最终变量
// 一个最终变量 final int THRESHOLD = 5; // 声明一个最终变量 final int THRESHOLD; // 一个静态的最终变量 PI static final double PI = 3.141592653589793; // 声明一个静态的最终变量 PI static final double PI;
当一个变量添加了 final
修饰符时,就表示该变量的值是不可变更的。因为不能修改变量的值。所以,实际上就是常量。
因为变量一旦添加了 final
修饰符就不能修改值,所以,该变量的值必须在声明时同时初始化。
如果最终变量保存的值是一个引用 ( 比如对象 ),那么意味着我们不能重新给它赋值以引用另一个对象,但该引用变量指向的对象的内部状态可以更改。例如,我们可以添加或删除最终数组或最终集合中的元素。
最终变量的变量名一般以全部大写形式表示,如果包含多个单词,可以使用下划线 ( _
) 分隔每个单词
最终变量的初始化
最终变量必须被正确的初始化,否则会在编译时抛出一个编译错误。
最终变量只能被初始化一次,要么在定义最终变量时使用赋值语句初始化,要么使用 实例化块 来初始化。
因为最终变量分为实例最终变量和静态最终变量,因此,最终变量的初始化方式也分为三种
-
定义时初始化 : 可以在声明最终变量时对其进行初始化。这种方式最常见。如果在声明时未初始化,则最终变量称为 「 空白最终变量 」。下面的两种方式可以对 空白最终变量 进行初始化操作。
-
初始化块 : 一个 空白最终变量 可以在 实例初始化块 中进行初始化,也可以在构造函数中进行初始化。
如果一个类有多个构造函数,则必须在所有构造函数中初始化,否则将抛出编译时错误。但这种情况下,我们一般使用 实例初始化块。
-
静态块 : 一个空白的静态的最终变量只能在 静态块 中进行初始化
范例
我们写一个范例来演示下最终变量的三种初始化方式。
JavaTester.java
public class JavaTester { // 最终变量在声明时同时初始化 final int THRESHOLD = 5; // 声明一个空白最终变量 final int CAPACITY; // 声明另一个空白最终变量 final int MINIMUM; // 静态最终变量在声明时同时初始化 static final double PI = 3.141592653589793; // 一个空白的静态的最终变量 static final double EULERCONSTANT; // 实例初始化块 // 用于初始化空白最终变量 CAPACITY { CAPACITY = 25; } // 静态块 // 用于初始化静态最终变量 EULERCONSTANT static{ EULERCONSTANT = 2.3; } // 构造函数 // 在构造函数中初始化最终变量 MINIMUM // // 注意: 如果有多个构造函数,则必须所有的构造函数中都必须对 MINIMUM 进行初始化 public JavaTester() { MINIMUM = -1; } public static void main(String[] args) { JavaTester a = new JavaTester(); } }
什么时候使用最终变量 ?
普通变量和最终变量之间的唯一区别是: 普通变量可以多次的重复的赋值,但最终变量一旦赋值,就无法继续再变更。
因此,最终变量只能用于整个程序执行期间需要保持不变的值。
引用最终变量
当最终变量的值为一个对象或集合时。我们称这个最终变量为 「引用最终变量」。
例如,下面的代码定义了一个 StringBuffer
类型的最终变量
final StringBuffer sb
正如前面我们所阐述的,最终变量无法重新分配。但如果最终变量是一个引用最终变量,我们可以更改该引用变量指向的对象的内部状态。
注意: 这不是重新分配。
final
这个修饰符是 非传递性。
我们写一段代码来演示下什么是对象内部状态。
public class JavaTester { public static void main(String[] args) { final StringBuilder sb = new StringBuilder("简单教程"); System.out.println(sb); sb.append("简单编程"); System.out.println(sb); } }
编译运行上面的实例,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester 简单教程 简单教程简单编程
这种 final
关键字的非传递性同样适用于数组,因为在 Java 语言中,数组也是一个对象。
带有
final
关键字的数组也称为最终数组。
最终变量的几点注意事项
-
如果上面所阐述的,最终变量不能重新在分配值,否则会报编译错误。
public class JavaTester { static final int CAPACITY = 4; public static void main(String args[]) { CAPACITY = 5; } }
编译运行这段代码,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester JavaTester.java:7: 错误: 无法为最终变量CAPACITY分配值 CAPACITY = 5; ^ 1 个错误
-
在类的方法/构造函数/块 中创建一个最终变量时,这个最终变量称之为 局部最终变量,它必须在创建它的地方初始化一次。
例如下面的代码,可以正常运行
public class JavaTester { static final int CAPACITY = 4; public static void main(String args[]) { final int i; i = 20; System.out.println(i); } }
编译运行以上代码,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester 20
-
如果你同时会 C++,那么一定要注意 C++ 中的
const
变量和 Javafinal
变量之间的区别。C++ 中的
const
变量要求在声明时必须同时初始化。但 Java 中则不是必须的,因为可以先声明一个空白最终变量,然后稍后再初始化。
-
Java
for
循环中的最终变量。在for
循环中,迭代的变量可以添加final
修饰符。这是合法的。public class JavaTester { static final int CAPACITY = 4; public static void main(String args[]) { int arr[] = {1, 2, 3}; for (final int i : arr) System.out.print(i + " "); System.out.println(); } }
编译运行上面这段代码,输出结果如下
[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester 1 2 3
在上面这个
for
循环中,由于i
变量在循环的每次迭代时超出范围,因此实际上每次迭代都重新声明,允许使用相同的标记(即i
)来表示多个变量。
最终类 ( final class )
当一个类添加了 final
修饰符后,该类就变成了 「 最终类 」 ( final class )。对于一个最终类,它的最大特性就是不能够被扩展,也不能被继承。
最终类一般有两个使用场景
-
阻止被继承。因为最终类不能够被继承。例如,所有包装类如
Integer
,Float
等都是最终类。我们无法扩展它们。final class A { // methods and fields } // 这个 B 类是不合.法的。 class B extends A { // 产生一个编译错误 COMPILE-ERROR! Can't subclass A }
-
最终类的另一个用途是创建一个 不可变的类 ( immutable class )。例如预定义的类
String
。在 Java 语言中,如果不让一个类成为最终的,我们就无法使一个类成为不可变的。
最终方法 ( final method )
当给一个方法添加上 final
关键字时,那么这个方法就变成了 「 最终方法 」 。 一个最终方法最大的特征就是它不能被 重写 ( override )。就像 Object
类中的大多数方法一样。
如果我们要一个方法在所有的派生类中都有着同样的实现 ( 同一份拷贝 ),则必须使用 final
关键字来修饰该方法。
例如下面的代码,如果在子类中重写最终方法,则会抛出一个编译错误
class A { final void m1() { System.out.println("This is a final method."); } } class B extends A { void m1() { // COMPILE-ERROR! Can't override. System.out.println("Illegal!"); } }