1.修饰类
final class Test {}
- 用final修饰一个类时,表明这个类不能被继承
- final类中的所有成员方法都会被隐式地指定为final方法(因为类不被继承,那么方法自然就不能被覆盖了)
- 尽量不要将类设计为final,除非以后真的不会继承这个类或出于安全考虑
2.修饰方法
public final void func() {}
- 用final修饰一个方法,表明这个方法不能被覆盖(override)
- 可以重载final修饰的方法
- 所有private方法会隐式地指定为final(因为无法取用private方法)
3.修饰变量
final int i = 0; //基本数据类型
final Object obj = new Object(); //引用类型
//允许'空白final'
final int x;
final Object y;
- 对于基本数据类型的变量,其值在初始化之后便不能更改,需要保证其在使用之前被初始化赋值
- 对于引用类型的变量,在初始化之后便不能再指向另一个对象(但对象自身是可以被修改的),必须在定义时或者在构造器中进行初始化赋值
- 允许'空白final',只要保证在第一次访问数据之前已在构造函数或构造代码块中将其初始化,只能二选一,不能同时使用构造函数和构造代码块进行初始化
final成员变量
- 如果final修饰的是类的成员变量,那这个变量必须在定义时被初始化,或者在类的构造函数中初始化
- 接口中的变量默认是public static final,因为static表示只有一个副本,所以要用final限制实现接口的类对该变量进行修改(一旦某个类修改了这个变量,其他实现此接口的类中的这个变量也会被修改)
final参数
在方法范围内,final参数的值或者指向对象无法被修改。这一特性主要用来向匿名内部类传递数据,匿名类中所有变量都必须是final变量
编译时常量
以下几种情况的定义会被当作编译时常量,即在编译期间能知道它的确切值(通常在定义时就被初始化),并在编译时直接将其替换成字面值
static final int VALUE = 1; // static + final
final int a = 1+2; // final变量是基本数据类型或String类型,且在编译时能知道确切值
final String b = "hello";
- 必须是基本数据类型或String类型
- 告诉编译器这块数据是不变的,可在编译时执行运算式,减轻运行时的负担
举个例子
String a = "hello2";
final String b = "hello"; //b被当成了编译时常量,遇到b会替换成"Hello"
String c = "hello";
String d = b + 2; //对编译器而言,这句和 String d = "hello"+2; 是一样的
String e = c + 2;
System.out.println((a == d)); //输出True
System.out.println((a == e)); //输出False
反编译字节码
编译后使用反编译工具javap
,在命令行输入javap -v <类名>.class
。其实不一定要用-v
,-c
也可以的,只是展示的信息量不同,更多用法见javap -help
Constant pool:
#1 = Methodref #18.#45 // java/lang/Object."<init>":()V
#2 = String #46 // hello2
#3 = String #47 // hello
#4 = InvokeDynamic #0:#51 // #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
#5 = Fieldref #52.#53 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #38.#54 // java/io/PrintStream.println:(Z)V
#38 = Class #68 // java/io/PrintStream
#51 = NameAndType #71:#72 // makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
#52 = Class #73 // java/lang/System
#53 = NameAndType #74:#75 // out:Ljava/io/PrintStream;
-------------------------------------------------------------------------------
Code:
0: ldc #2 // String hello2
2: astore_1 // 存储变量a
3: ldc #3 // String hello
5: astore_2 // 存储变量b
6: ldc #3 // String hello
8: astore_3 // 存储变量c
9: ldc #2 // String hello2
11: astore 4 // 存储变量d
13: aload_3
14: invokedynamic #4, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
19: astore 5 // 存储变量e
// ldc:(入栈)常量值从常量池中推送至栈顶
// astore:(出栈)将栈顶引用型数值存入指定本地变量
// astore_1 等同于 astore 1,astore 4 等同于 astore_4,此处不必纠结
// invokedynamic:(调用方法)表明调用点要实际执行哪个方法
从例子中可以看到,d的值是定义时直接以字面值确定的(没有访问b),就像a、b、c一样,而e的值是先访问了c的值、再进行字符串连接才得到的