final关键字
不同的使用环境下,final
关键字的含义有细微差别,但通常它指“这是无法改变的”。
final数据 #
final
关键字通常用来表示一块数据是恒定不变的:
常量 #
比如一个永不改变的
编译时常量
---> 对应前文所谓静态常量
:public final double PI = 3.14
如果使用
static final
来修饰变量,那么它就是一个标准的编译时常量(静态常量)。public static final double PI = 3.14
在Java中,编译时常量必须是基本数据类型或字符串,声明时必须赋值。
一个在
运行时被初始化
的值,你不希望它被改变public final int RANDOM = new Random().nextInt(47)
一个既是final
也是static
的域只占用一段不能改变的存储空间
当对非基本数据类型使用final
时,其含义稍微有点变化,其使引用恒定不变,这就是说被final
修饰的对象引用无法使其指向另一个对象,但是对象内容却是可以被修改的。
1public class FinalProperty {
2
3 final int a = 1;
4
5 final String s = "java";
6
7 final int[] a_array = new int[3];
8
9 static final String[] gender = new String[] { "male", "female" };
10
11 void test() throws Exception {
12 // illegal, cannot modify final fields
13 // a--
14 // s= "c++";
15
16 // use reflect can change final fields (before jdk 9)
17 // Field value = String.class.getDeclaredField("value");
18 // value.setAccessible(true);
19 // char[] chars = (char[]) value.get(s);
20 // chars[0] = 'J';
21 // System.out.println(s);
22
23 // object content can be modified
24 a_array[0] = 9;
25 a_array[1] = 2;
26 a_array[2] = 5;
27
28 Arrays.sort(a_array);
29
30 System.out.println(Arrays.toString(a_array));
31
32 String[] Gender = new String[] { "male", "female" };
33 // not allowed! cannot reference final
34 // property to a new reference!
35 // gender = Gender;
36 gender[0] = "MALE";
37 System.out.println(Arrays.toString(gender));
38
39 }
40
41 public static void main(String[] args) throws Exception {
42 new FinalProperty().test();
43 }
44}
45
46/*
47 * output:
48 * [2, 5, 9]
49 * [MALE, female]
50 *///:~
Unable to make field private final byte[] java.lang.String.value accessible
这个限制,即无法通过反射访问
java.lang.String
类的私有 value 字段,是在Java 9中引入的模块化系统 (Project Jigsaw) 的一部分。更具体地说,这是由强封装(strong encapsulation) 机制强制执行的。 在Java 9之前,虽然 value 字段是私有的,但仍然可以通过反射 API 的
setAccessible(true)
方法来访问和修改它。 Java 9引入了模块的概念,并且默认情况下,模块不会导出其内部实现细节。java.lang
包属于java.base
模块,而java.base
模块默认情况下不会导出java.lang.String
的内部字段。因此,从Java 9开始,尝试通过反射访问
java.lang.String
的 value 字段会抛出java.lang.IllegalAccessException
异常,除非你采取一些特殊的措施来绕过模块系统的限制 (例如,使用 --add-opens 命令行参数)
Java允许声明一个空白final域
,但是这个final
域在使用前必须使用构造器(常见)或表达式初始化。
1// 在构造器中初始化 final 域
2public class MyClass {
3 private final int x;
4
5 public MyClass(int value) {
6 x = value; // 在构造器中初始化 final 域
7 }
8
9 public int getX() {
10 return x;
11 }
12}
13// 在实例初始化块中初始化 final 域
14public class MyClass {
15 private final int x;
16
17 {
18 x = 10; // 在实例初始化块中初始化 final 域
19 }
20
21 public MyClass() {
22 // 构造器可以为空,因为 x 已经在实例初始化块中初始化了
23 }
24
25 public int getX() {
26 return x;
27 }
28}
29// 在静态初始化块中初始化 final 域
30public class MyClass {
31 private static final int x;
32
33 static {
34 x = 10; // 在静态初始化块中初始化静态 final 域
35 }
36
37 public static int getX() {
38 return x;
39 }
40}
参数 #
此外,Java允许参数列表中将参数指明为final
,这意味着你无法在方法中更改参数引用所指向的对象,但是对象状态也是可变的。
注意此处的表述。作为对比,Java引用对象作为参数时, 参数引用指向的对象是可以改变的(尽管这个改变不能应用到对象引用上---对象引用不可变)
Employee2使用之前的代码:
1public class FinalParam {
2
3 static void with(final Employee e) {
4 e.raiseSalary(3);
5 System.out.println("salary of e = " + e.getSalary());
6 }
7
8 static void swap(final Employee j, final Employee k) {
9 Employee tmp = j;
10 // check error, final var assign is not allowed
11 // k = temp;
12 // j = k;
13 }
14
15 public static void main(String[] args) {
16 Employee x = new Employee("ali", 1000);
17 with(x);
18 System.out.println("salary of x = " + x.getSalary());
19 }
20}
21
22/* output:
23 * salary of e = 3000
24 * salary of x = 3000
25 */// :~
final
参数一般用来向匿名内部类中传递数据。
实际上,Java并没有提供任何对象恒定不变的途径,虽然可以通过编程取得对象恒定不变的效果单例。
final方法--阻止继承 #
当将一个方法声明为final
时,其主要作用就是锁定方法,阻止继承,这往往是出于设计的考虑。
需要特殊说明的是:如果一个方法是private
的,那么它就被隐式
地声明为final
了,因为由于无法取用private
方法,也就无法覆盖之。
参考如下代码:
1public class FinalMethodT {
2 public static void main(String[] args) {
3 FinalMethodExt x = new FinalMethodExt();
4 x.f();
5 x.g();
6 x.p();
7 System.out.println("----");
8 // upcast
9 FinalMethod y = x;
10 y.f();
11 y.g();
12 // y.p(); // can't access
13 System.out.println("----");
14 FinalMethod z = new FinalMethod();
15 z.f();
16 z.g();
17 // z.p(); // can't access
18 }
19}
20
21class FinalMethod {
22 void f() {
23 System.out.println("f()");
24 }
25
26 final void g() {
27 System.out.println("g()");
28 }
29
30 private void p() {
31 System.out.println("p()");
32 }
33}
34
35class FinalMethodExt extends FinalMethod {
36
37 @Override
38 void f() {
39 // super.f();
40 System.out.println("ext f()");
41 }
42 // cannot override
43 // final void g(){ System.out.println("ext g()"); }
44
45 final void p() {
46 System.out.println("ext p()");
47 }
48}
49
50/* output:
51 * ext f()
52 * g()
53 * ext p()
54 * ----
55 * ext f()
56 * g()
57 * ----
58 * f()
59 * g()
60 */
基类和导出类的p
方法,看上去像是导出类覆盖了基类的方法,实际上这是一种“字面”的覆盖,因为private
方法并不是基类接口的一部分,它和导出类的p
方法只是具有相同名称而已。
还需要注意的是,即使使用了向上转型
试图调用基类的方法,实际上却失败了,因为子类覆盖了这个方法。这种行为在Java中被称为动态绑定
,即编译器知道实际引用的对象类型,从而去调用对应的方法。
final类--阻止继承 #
如果将一个类声明为final
的(通常使用final class ...
),意味着这个类不能被继承。
也就是说,该类的所有方法都含有一个隐式的final
修饰符。
final
类的域可以是或不是final
,规则同
final数据一致。
参考: https://docs.oracle.com/javase/specs/jls/se18/html/jls-17.html#jls-17.5