0%

final修饰符

一、含义

final修饰符能够修饰“类,变量,方法”。

1.1、类

final修饰符修饰“类”,表示该类不允许被继承,因此“类”只能是“具体类,枚举”,而不能是“抽象类,接口,注解”。

1.2、变量

final修饰符修饰“变量”,表示变量在被使用前必须“被显式初始化,且只能被显式初始化一次”。
“只能被显式初始化一次”的含义:如果是基本类型变量,只能被显式初始化一次,不能进行第二次显式初始化,即第一次显式初始化后变量值保持不变;如果是引用类型变量,只能被显式初始化一次,不能进行第二次显式初始化,即第一次显式初始化后变量值保持不变,但是需要注意的是,引用类型变量所指对象的内容是允许被改变的。
“变量”分为3种:“字段,方法形式参数变量,局部变量”。

1.2.1、字段

字段的“默认初始化”不被认为是“显式初始化”,只有“定义初始化,实例初始化语句初始化,构造器初始化”(针对普通字段)和“定义初始化,静态初始化语句初始化”(针对静态字段)才被认为是“显式初始化”。

1.2.2、方法形式参数变量

方法形式参数变量的初始化天然的是“显式初始化”,因为不存在“默认初始化”。

1.2.3、局部变量

局部变量的初始化天然的是“显式初始化”,因为不存在“默认初始化”。

1.3、方法

final修饰符修饰“方法”,表示该方法不允许被覆盖,因此显而易见的是:final修饰符不能跟abstract修饰符共用。

二、编译期常量

字段或者局部变量(方法形式参数变量不能进行定义初始化,因此天然不满足成为编译期常量的基本条件)只有满足以下两个条件时才能被称为“编译期常量”:

  1. 由final修饰符修饰
  2. 字段或者局部变量为“基本类型或者String类型”,进行“定义初始化”,且在定义初始化时使用“确切的值”进行赋值,而不需要通过间接的运算(虚拟机会对满足条件的+,-,*,/等运算进行编译期优化,具体的条件描述笔者不知,可认为这些经过编译期优化的+,-,*,/等运算不属于上述“间接的运算”)

本质上,“类”只包括“具体类和接口”,为进行完备介绍,分别以具体类和接口中的编译期常量进行举例说明。

备注
接口中的字段默认由“public static final”修饰符组合修饰。

2.1、编译期常量识别和混淆

以下示例代码给出了几个“编译期常量”和“易被混淆为编译期常量”的例子。

具体类中的编译期常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package modifier;

import java.util.Random;

class CompileConstantExampleClass {

private static final Random rand = new Random();

// 编译期常量
final int a = 10;

// 编译期常量
static final boolean b = true;

// 编译期常量
final String c = "hello";

// 编译期常量
static final String d = "world";


// 非编译期常量
final int e = new Integer(10);

// 非编译期常量
static final int f = rand.nextInt(10);

// 非编译期常量
final String g = new String("hello");

// 编译期常量,编译期优化:编译时完成拼接
static final String h = "wor" + "ld";

// 编译期常量
static final int p = 10 + 20;

// 编译期常量
static final String q = d + " beautiful";

// 编译期常量
static final int r = 10 - 20;

// 编译期常量
static final int s = 10 * 20;

// 编译期常量
static final int t = 20 / 10;

// 非编译期常量
static final String i = "world" + rand.nextInt(10) + "ld";

// 非编译期常量
final int j;

{
j = 20;
}

// 非编译期常量
static final String k;

static {
k = "world";
}

public static void main(String[] args)
{
System.out.println(CompileConstantExampleClass.b);
System.out.println(CompileConstantExampleClass.d);
System.out.println(CompileConstantExampleClass.f);
System.out.println(CompileConstantExampleClass.h);
System.out.println(CompileConstantExampleClass.i);
System.out.println(CompileConstantExampleClass.k);
System.out.println(CompileConstantExampleClass.p);
System.out.println(CompileConstantExampleClass.q);
System.out.println(CompileConstantExampleClass.r);
System.out.println(CompileConstantExampleClass.s);
System.out.println(CompileConstantExampleClass.t);

CompileConstantExampleClass compileConstantExampleClass = new CompileConstantExampleClass();
System.out.println(compileConstantExampleClass.a);
System.out.println(compileConstantExampleClass.c);
System.out.println(compileConstantExampleClass.e);
System.out.println(compileConstantExampleClass.g);
System.out.println(compileConstantExampleClass.j);

// 编译期常量
final int l = 10;

// 编译期常量
final String m = "world";

// 非编译期常量
final int n = rand.nextInt(10);

// 非编译期常量
final String o = "hell" + rand.nextInt(10) + "o";

System.out.println(l);
System.out.println(m);
System.out.println(n);
System.out.println(o);

}

}

接口中的编译期常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package modifier;

import java.util.Random;

public interface CompileConstantExampleInterface {

int a = 10;

String b = "hello";

Random rand = new Random();

int c = rand.nextInt(10);

String d = "he" + rand.nextInt(10) + "llo";

}

class VisitClass {

public static void main(String[] args) {
System.out.println(CompileConstantExampleInterface.a);
System.out.println(CompileConstantExampleInterface.b);
System.out.println(CompileConstantExampleInterface.c);
System.out.println(CompileConstantExampleInterface.d);
}

}

2.2、编译期常量访问

根据Java编程思想#复用类可知,第一次访问类的“静态字段”会触发执行“类对象初始化”,但是有个例外:如果“静态字段”是“编译期常量”,则第一次访问该“静态字段”并不会触发执行“类对象初始化”。
具体原因:虚拟机会对访问“编译期常量”的源代码进行编译期优化,优化后等效于“直接配定相应常量值,而不再引用原字段或者局部变量”,因此自然不会触发执行“类对象初始化”。
以下示例代码给出了几个例子。

具体类中的编译期常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package modifier;

class A {

static {
System.out.println("触发类对象初始化的标志");
}

static int a = 10;

static final int b = 20;

static String c = "hello";

static final String d = "world";

static final String e = "wor" + "ld";
}

public class CompileConstantInClass {

public static void main(String[] args) {

//每次只访问A中一个字段,进行验证

// System.out.println(A.a);
// System.out.println(A.b);
// System.out.println(A.c);
// System.out.println(A.d);
System.out.println(A.e);
}

}

接口中的编译期常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package modifier;

import java.util.Random;

interface B {

Random rand = new Random();

int a = 10;

int b = C.f();

int c = rand.nextInt(10);

String d = "hel" + "lo";
}

class C {

public static int f() {
System.out.println("调用C类的f()方法");
return 10;
}

}

public class CompileConstantInInterface {

public static void main(String[] args) {

//每次只访问B中一个字段,进行验证

// System.out.println(B.a);
// System.out.println(B.b);
// System.out.println(B.c);
System.out.println(B.d);

}
}

**备注:** 可借助`javap -c`命令进行编译期常量的相关验证。
您的支持将鼓励我继续分享!