一、含义
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、编译期常量识别和混淆
以下示例代码给出了几个“编译期常量”和“易被混淆为编译期常量”的例子。
具体类中的编译期常量:
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) {
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) {
System.out.println(B.d);
} }
|
**备注:**
可借助`javap -c`命令进行编译期常量的相关验证。