一、含义
final修饰符能够修饰“类,变量,方法”。
1.1、类
final修饰符修饰“类”,表示该类不允许被继承,因此“类”只能是“具体类,枚举”,而不能是“抽象类,接口,注解”。
1.2、变量
final修饰符修饰“变量”,表示变量在被使用前必须“被显式初始化,且只能被显式初始化一次”。
“只能被显式初始化一次”的含义:如果是基本类型变量,只能被显式初始化一次,不能进行第二次显式初始化,即第一次显式初始化后变量值保持不变;如果是引用类型变量,只能被显式初始化一次,不能进行第二次显式初始化,即第一次显式初始化后变量值保持不变,但是需要注意的是,引用类型变量所指对象的内容是允许被改变的。
“变量”分为3种:“字段,方法形式参数变量,局部变量”。
1.2.1、字段
字段的“默认初始化”不被认为是“显式初始化”,只有“定义初始化,实例初始化语句初始化,构造器初始化”(针对普通字段)和“定义初始化,静态初始化语句初始化”(针对静态字段)才被认为是“显式初始化”。
1.2.2、方法形式参数变量
方法形式参数变量的初始化天然的是“显式初始化”,因为不存在“默认初始化”。
1.2.3、局部变量
局部变量的初始化天然的是“显式初始化”,因为不存在“默认初始化”。
1.3、方法
final修饰符修饰“方法”,表示该方法不允许被覆盖,因此显而易见的是:final修饰符不能跟abstract修饰符共用。
二、编译期常量
字段或者局部变量(方法形式参数变量不能进行定义初始化,因此天然不满足成为编译期常量的基本条件)只有满足以下两个条件时才能被称为“编译期常量”:
- 由final修饰符修饰 
- 字段或者局部变量为“基本类型或者String类型”,进行“定义初始化”,且在定义初始化时使用“确切的值”进行赋值,而不需要通过间接的运算(虚拟机会对满足条件的+,-,*,/等运算进行编译期优化,具体的条件描述笔者不知,可认为这些经过编译期优化的+,-,*,/等运算不属于上述“间接的运算”)
本质上,“类”只包括“具体类和接口”,为进行完备介绍,分别以具体类和接口中的编译期常量进行举例说明。
备注:
接口中的字段默认由“public static final”修饰符组合修饰。
2.1、编译期常量识别和混淆
以下示例代码给出了几个“编译期常量”和“易被混淆为编译期常量”的例子。
具体类中的编译期常量:
| 12
 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);
 
 }
 
 }
 
 | 
接口中的编译期常量:
| 12
 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编程思想#复用类可知,第一次访问类的“静态字段”会触发执行“类对象初始化”,但是有个例外:如果“静态字段”是“编译期常量”,则第一次访问该“静态字段”并不会触发执行“类对象初始化”。
具体原因:虚拟机会对访问“编译期常量”的源代码进行编译期优化,优化后等效于“直接配定相应常量值,而不再引用原字段或者局部变量”,因此自然不会触发执行“类对象初始化”。
以下示例代码给出了几个例子。
具体类中的编译期常量:
| 12
 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) {
 
 
 
 
 
 
 
 System.out.println(A.e);
 }
 
 }
 
 | 
接口中的编译期常量:
| 12
 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) {
 
 
 
 
 
 
 System.out.println(B.d);
 
 }
 }
 
 | 
**备注:**
可借助`javap -c`命令进行编译期常量的相关验证。