final核心思想就是“不可变”或者“最终版”。把它加在不同的地方,作用略有不同,但都围绕着这个核心。
可以把final想象成一种“封印”或者“刻在石头上”的标记。
一、final修饰变量
通俗解释:给变量贴上一张“只读”标签。一旦你给它赋了值,它就再也不能改变了,就像刻在石头上的字一样。它变成了一个常量。
详细说明:
基本类型变量 (int, double, char, boolean 等): 值不能再改变。
final int MAX_SIZE = 100;
MAX_SIZE = 200; // 编译错误!不能再改了,MAX_SIZE 已经是 100了。
引用类型变量 (对象、数组): 引用本身不能再指向另一个对象或数组。但是!对象内部的状态(成员变量的值)或者数组里的元素 还是可以修改的!这点最容易混淆。
final List<String> myList = new ArrayList<>();
myList.add(“Hello”); // 允许!修改的是对象内部的内容
myList.add(“World”); // 允许!
myList = new LinkedList<>(); // 编译错误!不能再让 myList 指向另一个 List 对象了。
// 石头上的字(myList 指向的地址)不能变,但石头里面的东西(ArrayList 里的元素)可以动。
成员变量 (Instance Variable): 必须在声明时、或者在每个构造方法中、或者在初始化块中显式地赋值一次且仅一次。之后不能再改。
静态变量 (Static Variable / Class Variable): 必须在声明时、或者在静态初始化块中显式地赋值一次且仅一次。之后不能再改。
局部变量 (Local Variable): 可以在声明时赋值,也可以只声明不赋值,但在第一次使用之前必须赋值一次且仅一次。之后不能再改。
final int localVar;
if (someCondition) {
localVar = 10; // 第一次赋值
} else {
localVar = 20; // 第一次赋值 (走这个分支)
}
// localVar = 30; // 编译错误!已经赋值过了,不能再改。
为什么用?
定义常量:列如 Math.PI。
提高可读性和安全性:明确告知别人和编译器这个值不应该改变,防止意外修改。
配合匿名内部类:匿名内部类访问外部方法的局部变量时,该局部变量必须是final或等效final(Java 8+)。这是由于局部变量在方法执行完就没了,而内部类对象可能还存在,为了保证内部类访问到的值是一致的,Java复制了一份值过去,要求原值不变才能保证复制的值有效。
二、final修饰方法
通俗解释:给方法贴上“禁止后代重写”的封条。这个方法就是最终版本,子类不能用自己的实现来覆盖它。
详细说明:
被final修饰的方法不能被子类覆盖 (Override)。
父类的final方法,子类只能老老实实继承使用,不能改变实则现。
class Parent {
public final void doSomethingImportant() {
// 关键操作,必须这样执行
System.out.println(“Parent's important work”);
}
}
class Child extends Parent {
// @Override // 加上这个注解会更明显看出错误
public void doSomethingImportant() { // 编译错误!不能覆盖父类的 final 方法
System.out.println(“Trying to change important work…”);
}
}
为什么用?
防止关键行为被篡改:父类设计者认为这个方法的行为至关重大且固定,不允许子类以任何方式改变实则现逻辑,确保核心功能的一致性。例如,Object类中的 getClass() 方法是final的,由于获取对象类型这个行为必须是标准且不可变的。
性能优化 (历史缘由/争议):早期JVM可能对final方法有轻微内联优化(编译器直接替换方法调用为方法体代码)。现代JVM超级智能,即使没有final,也能很好地判断哪些方法可以安全内联。所以性能不再是主要思考因素。 设计意图(禁止重写)才是重点。
安全/设计约束:确保方法的行为符合父类设计者的预期。
三、final修饰类
通俗解释:给整个类贴上“禁止开枝散叶”的封印。这个类就是家族的最后一代,不能有子类了。
详细说明:
被final修饰的类不能被任何其他类继承。
它自己不能是父类。
final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
// 只有 getter, 没有 setter, 由于字段也是 final
}
class SpecialPoint extends ImmutablePoint { // 编译错误!不能继承 final 类
// …
}
为什么用?
设计不可变类 (Immutable Class):这是最重大的缘由!如果一个类本身不可被继承(final),并且所有字段都是private final,且不提供修改字段的方法(或者通过防御性拷贝返回),那么这个类创建的对象状态就是真正不可变的。例如Java标准库中的String, Integer, Double等包装类都是final。不可变对象在并发编程(线程安全)、缓存、作为Map键等方面有巨大优势。
安全:防止恶意子类化破坏类的行为(列如覆盖方法改变逻辑)。例如 String 如果是可继承的,就可能有人创建伪装的String对象破坏安全性。
设计意图清晰:明确告知使用者,这个类就是最终形态,没有扩展点,不要尝试继承它来做修改。
四、final修饰方法参数
通俗解释:给传入方法内部的参数值贴上一张“只读”标签(针对基本类型)或给传入的“遥控器”贴上一张“不能换遥控对象”的标签(针对引用类型)。
详细说明:
在方法内部,不能再给这个参数变量本身赋值。
对于基本类型:不能改变它的值。
对于引用类型:不能让它指向另一个对象(和final局部变量一样)。但对象内部的状态依旧可以修改(除非对象本身是不可变的)。
public void processValue(final int input) {
// input = 10; // 编译错误!不能修改 final 参数的值
System.out.println(input);
}
public void modifyList(final List<String> list) {
// list = new ArrayList<>(); // 编译错误!不能让它指向新列表
list.add(“Modified”); // 允许!修改的是列表里面的内容
}
为什么用?
明确意图:告知方法内部的代码(以及读代码的人),这个参数不应该被重新赋值(指向新的引用)。有助于防止不小心修改了参数引用。
配合匿名内部类 (历史缘由):在Java 8之前,如果要在匿名内部类中使用外部方法的参数,该参数必须声明为final。缘由和局部变量一样(需要复制值保证一致性)。Java 8引入了“等效 final”的概念(变量虽然没有显式声明final,但在初始化后从未改变过),这种情况下可以不用显式写final,但效果一样。显式写上final依然清晰表明意图。
五、总结
final就是Java给你的一把“锁”或一支“刻刀”,让你在代码中明确标记出那些不应该再被改变的东西(值、引用、方法行为、类的结构)。用好它能提高代码的清晰度、安全性、可维护性,尤其是在设计不可变对象和框架时。
















暂无评论内容