Java 的基础知识,final 关键字。想毕从事 Java 开发工作的朋友对 final 关键字都不会陌生,但是对它有非常全面清晰认识的,我觉得不多。final 有哪些功能,为何要使用 final 数据,何时使用 final 数据呢?可以带着疑问来看此文。内容比较细致,可能有些啰嗦,请耐心的看完,希望能对大家有所帮助,读后有所解惑。
Java 的 final 关键字根据上下文应用场景的不同而含义略有不同,但总的来说,用它修饰定义,则表示被修饰的目标不可更改。通常出于两个原因想要禁止目标的更改:设计或效率。因为这两个原因完全不同,所以可能会误用 final 关键字。
Java 程序编写中可以使用 final 关键字修饰的三类目标:数据、方法和类。
请注意,此文我仅讲述对 final 关键字对数据变量定义的修饰使用。
像许多其他编程语言一样,java 编程语言也有方式告诉编译器一份数据是“常数”,就是所谓的常量。之所以使用常量,看重的是它本身的特点,有两个原因:
它可以是一个永远不会改变的编译时常量。
它可以是在运行时初始化且不可再更改的值。
当你有想让你程序里的数据满足上述两种情况的需求时,就可以使用 final 关键字了。
对于编译时常量,允许编译器将常量值“封装”到使用它的任何计算中;也就是说,编译时常量,在编译期间就可以计算使用,这样可以节省一些运行时的开销。在 Java 编程语言中,编译时常量的数据类型, 必须是 final 关键字修饰的基本数据类型。在进行定义时必须给一个初始值。
当同时使用 static 和 final 两关键字修饰定义一个变量时,表示这个变量在整个程序中,只有一份数据存储,且是不可变的。这里 static 强调只有一份,final 表示常量,不可变。~有点 C 语言静态数据的味道~
Java 语言中的变量,包括类对象变量、基本数据类型变量、数组变量等,抽象概括的说就是两种:一种称为类对象变量,另一种基本数据类型变量。
当用 final 关键字修饰定义变量的数据类型是类类型,而不是基本数据类型时,此时的语义容易混淆,最终结果是,前者将对象引用(对象引用,可理解为访问对象的地址。类似地,windows c++系统开发中,也称之为句柄。)定义成了常数,便于理解可以叫对象引用常量,后者将变量值定义成了常数。这里,一旦此对象引用被初始化指向一个对象,它将永远不再被允许指向其他的对象,但是对象本身的成员数据是可以改变的。Java 没有提供一种使得任意对象本身是常量的方法(但我们可以自己编写代码,达到让对象本身是常量的效果)。这里注意,数组类型数据,加 final 关键字定义,成为常量的部分是对象引用,因为数组同样可以理解是类类型,不是基本数据类型。
最好通过例子来体会一下,
import java.util.*;
class Value { int i; // 包内可访问 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; } // 可以是编译时常量: private final int valueOne = 9; private static final int VALUE_TWO = 99; // 典型的公共常量: public static final int VALUE_THREE = 39; // 不能是编译时常量: 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); // 数组: 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++; // 错误: 不能改变值 fd1.v2.i++; // 不是常量! fd1.v1 = new Value(9); // OK -- not final for(int i = 0; i < fd1.a.length; i++) { fd1.a[i]++; // 不是常量! } //! fd1.v2 = new Value(0); // 错误: 不能改变对象引用 //! fd1.VAL_3 = new Value(1); // 错误: 不能改变对象引用 //! fd1.a = new int[3]; // 错误: 不能改变对象引用 System.out.println(fd1); System.out.println("创建新的 FinalData 对象"); FinalData fd2 = new FinalData("fd2"); System.out.println(fd1); System.out.println(fd2); } } /* Output: fd1: i4 = 15, INT_5 = 18 创建新的 FinalData 对象 fd1: i4 = 15, INT_5 = 18 fd2: i4 = 13, INT_5 = 18 *///:~
看一下程序中的 final 关键字的各种使用情况:
由于 valueOne 和 VALUE_TWO 是具有编译时值的最终原语,因此它们都可以用作编译时常量,并且在任何重要方面都没有区别。VALUE_THREE 是定义此类常量的更典型方式:public 表示它们可以在包外使用,static 强调只有一个,final 表示它是一个常量。请注意,具有恒定初始值(即编译时常量)的最终静态原语按照约定以全部大写字母命名,单词之间用下划线分隔。 (这就像 C 常量一样。)
有 final 的定义,并不意味着它的值在编译时就是已知的。这通过在运行时使用随机生成的数字初始化 i4 和 INT_5 可以来说明。示例的这一部分还显示了将最终值设为静态或非静态之间的区别。这种差异仅在运行时初始化值时才会出现,因为编译器编译时对值的处理方式是相同的。运行程序时才会显示差异。请注意, 对 fd1 和 fd2 来说, i4 的值是分别唯一的,就是说, fd1 的 i4 的值是唯一的, fd2 的 i4 的值是唯一的,但因 static 的原因 INT_5 的值不会通过创建第二个 FinalData 对象而更改。这是因为它是静态的,并且在加载时只初始化一次,而不是每次创建新对象都进行初始化。
变量 v1 到 VAL_3 展示了 final 引用的含义。正如你在 main() 中看到的,仅仅因为 v2 是 final 并不意味着你不能改变它的值。因为此出实际上 final 的是一个对象引用,所以 final 意味着不能将 v2 重新绑定到新对象,但是可以改变v2 对象本身。还可以看到,同样的语义对数组也是一样的,可以理解它只是另一种的对象引用。
通过上面的例子演示,是不是更容易理解一些。多动动手~