final 是 Java 中的一个关键字,它意为“最终的”、“不可变的”,当它被用来修饰不同类型的元素时,其作用也不同。final 关键字可以用来修饰类、方法和变量。

下面我们分别从这三个方面来阐述其作用。
修饰变量
当 final 修饰变量时,意味着该变量一旦被初始化赋值后,其值就不能再被修改,它本质上是一个常量。
根据变量类型的不同,其具体规则又分为三种情况:
a) 修饰基本数据类型 (如 int, double, char 等)
final 修饰的是基本数据类型的变量,那么变量的值不能被改变。

public class FinalExample {
final int FINAL_NUMBER = 10;
public void modifyValue() {
// FINAL_NUMBER = 20; // 编译错误!无法为最终变量FINAL_NUMBER分配值
System.out.println("FINAL_NUMBER is: " + FINAL_NUMBER);
}
}
b) 修饰引用数据类型 (如 String, ArrayList, 自定义类等)
final 修饰的是引用数据类型的变量,它意味着变量的引用(即它指向的内存地址)不能被改变,但是引用对象内部的状态(属性)是可以被修改的。
这是一个非常重要的区别,很多初学者会在这里混淆。
import java.util.ArrayList;
import java.util.List;
public class FinalReferenceExample {
final List<String> names = new ArrayList<>();
public void modifyReference() {
// 1. 这行代码会编译错误,因为names的引用地址不能改变
// names = new ArrayList<>();
// 2. 这是允许的,因为修改的是names所指向的ArrayList对象内部的内容
names.add("Alice");
names.add("Bob");
System.out.println("Names list: " + names); // 输出: [Alice, Bob]
}
}
c) 修饰成员变量(类的字段)
对于类的成员变量,final 变量必须被初始化,初始化的方式有两种:
- 声明时初始化(显式初始化):像上面的例子一样,在声明变量时就直接赋值。
- 在构造函数中初始化:如果声明时没有赋值,那么必须在所有的构造函数中对其进行初始化,Java 确保对象在创建完成前,
final成员变量一定被赋值。
public class FinalMemberExample {
final int id; // 声明时未初始化
// 必须在构造函数中初始化 final 成员变量
public FinalMemberExample(int id) {
this.id = id;
}
// 如果有多个构造函数,每个都必须初始化 id
public FinalMemberExample() {
this(0); // 调用另一个构造函数来初始化
}
// public FinalMemberExample(String name) {
// // 编译错误!构造函数中没有初始化 final 变量 id
// }
}
修饰方法
当 final 修饰方法时,意味着该方法不能被其子类重写(Override)。

这主要有两个目的:
- 锁定方法:防止任何子类修改其行为,保证了方法实现的稳定性和一致性。
- 性能优化:在 Java 早期版本(JDK 1.5 之前),
final方法可以被编译器内联(Inlining),即直接将方法调用处的代码替换为方法体本身,从而消除方法调用的开销,虽然现代 JVM 的即时编译器已经非常智能,即使没有final关键字也能进行大量的内联优化,但final仍然是一个明确的信号。
class Animal {
// final 方法,子类不能重写
public final void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal {
// @Override
// public void eat() { // 编译错误!无法重写最终方法
// System.out.println("Dog is eating.");
// }
public void bark() {
System.out.println("Dog is barking.");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.eat(); // 输出: Animal is eating. 调用的是父类的 final 方法
}
}
修饰类
当 final 修饰类时,意味着该类不能被继承。
它创建了一个“终极”类,任何试图继承它的行为都会导致编译错误。
这在以下场景中非常有用:
- 安全性:防止不可信的代码扩展或修改类的核心行为。
String类就是final的,这保证了字符串的不可变性,也防止了恶意代码通过继承String类来破坏其安全性。 - 设计意图:当一个类的设计意图就是作为工具类(只包含静态方法和静态字段)或一个不可变的实体时,将其声明为
final可以清晰地表明其不应被继承的设计意图。
// final 类,不能被继承
final class ImmutableClass {
private final int value;
public ImmutableClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
// class MyImmutableClass extends ImmutableClass { // 编译错误!无法继承最终类
// }
总结表格
| 修饰对象 | 作用 | 示例 | 主要目的 |
|---|---|---|---|
| 变量 | 值不可变 | final int MAX_AGE = 100;final List<String> list = new ArrayList<>(); |
定义常量 保证线程安全(不可变对象天生线程安全) 引用地址不可变 |
| 方法 | 不能被子类重写 | public final void doSomething() {...} |
锁定方法实现,防止被修改 (历史原因)优化性能 |
| 类 | 不能被继承 | public final class String {...} |
保证类的安全性和完整性 明确设计意图 |
一个重要的补充:final 与 private 的关系
你可能注意到,private 方法也隐式地是 final 的,因为 private 方法不能在子类中被访问,自然也就不能被重写。
class Parent {
private void privateMethod() {
System.out.println("Parent's private method");
}
}
class Child extends Parent {
// 这个方法并不是重写,而是 Child 类自己定义的一个新方法
// 因为它无法访问或重写父类的 privateMethod
public void privateMethod() {
System.out.println("Child's new method");
}
}
如果你将一个 private 方法显式地声明为 final,编译器会给出警告,因为这通常是多余的代码。
最佳实践
- 优先使用
final:如果你发现一个变量、方法或类不打算被修改或继承,就大胆地使用final,这能让代码意图更清晰,也能帮助编译器进行一些优化。 - 常量命名规范:对于
static final修饰的常量,按照 Java 约定,其命名应该全部使用大写字母,单词之间用下划线_分隔,MAX_VALUE和PI。 - 谨慎使用
final类:将类设为final会限制其扩展性,除非有非常充分的理由(如String类),否则应谨慎使用,更好的做法是通过良好的设计来避免继承被滥用。
