final 是 Java 中的一个关键字,它表示“最终的”、“不可变的”,当用它来修饰变量时,意味着这个变量的引用一旦被初始化后,就不能再指向其他对象。

这里有一个非常重要的区别,我们需要根据变量的类型来理解它的具体含义。
final 修饰基本数据类型
当 final 修饰一个基本数据类型(如 int, double, char, boolean 等)的变量时,它的含义非常直接和明确:
一旦给这个变量赋了初始值,就不能再修改这个值。
示例代码:

public class FinalPrimitiveExample {
// 在声明时初始化
final int a = 10;
// 在构造方法中初始化
final int b;
public FinalPrimitiveExample() {
this.b = 20; // 合法
// this.b = 30; // 编译错误!b 已经被初始化,不能再赋值
}
public static void main(String[] args) {
FinalPrimitiveExample example = new FinalPrimitiveExample();
System.out.println("a = " + example.a); // 输出 a = 10
System.out.println("b = " + example.b); // 输出 b = 20
// example.a = 100; // 编译错误!无法为 final 变量 a 指定值
}
}
关键点:
- 值不可变:
a的值永远是10,不能被改变。 - 初始化时机:
final基本类型变量必须在使用前被初始化,通常有两个地方:- 声明时:如
final int a = 10; - 构造方法中:如示例中的
b,这允许在对象创建时动态确定其值,但之后不能修改。
- 声明时:如
final 修饰引用数据类型
当 final 修饰一个引用数据类型(如自定义的类、数组、String 等)的变量时,它的含义就比较微妙了:
一旦给这个引用变量初始化(即让它指向一个对象),就不能再让它指向其他对象。
它所指向的对象内部的 state(状态,即成员变量)是可以被修改的。

示例代码:
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public class FinalReferenceExample {
// final 引用,在声明时初始化
final Person p1 = new Person("Alice");
// final 引用,在构造方法中初始化
final Person p2;
public FinalReferenceExample() {
this.p2 = new Person("Bob");
}
public static void main(String[] args) {
FinalReferenceExample example = new FinalReferenceExample();
// --- 不可操作 ---
// example.p1 = new Person("Carol"); // 编译错误!p1 不能指向新的对象
// example.p2 = new Person("David"); // 编译错误!p2 不能指向新的对象
// --- 可以操作 ---
// 修改 p1 所指向的对象的内部状态
example.p1.name = "Alice Updated";
System.out.println("p1's name is now: " + example.p1.name); // 输出 p1's name is now: Alice Updated
// 修改 p2 所指向的对象的内部状态
example.p2.name = "Bob Updated";
System.out.println("p2's name is now: " + example.p2.name); // 输出 p2's name is now: Bob Updated
}
}
图解说明:
-
final Person p1 = new Person("Alice");p1是一个引用,它像一根指向Person对象的箭头。final保证了这根箭头不能再指向别处。
-
example.p1.name = "Alice Updated";- 这句话没有改变
p1这个引用本身,而是通过p1找到它指向的那个Person对象,然后修改了该对象内部的name字段。 - 箭头还是指着同一个对象,但对象的内容变了。
- 这句话没有改变
final 修饰的特殊成员:static final
当 final 和 static 一起使用时,它定义的是一个静态常量。
static:表示这个变量属于类,而不是某个对象,所有实例共享这一个变量。final:表示它的值一旦初始化就不能改变。
约定俗成: static final 常量的命名通常使用全大写字母,单词之间用下划线 _ 分隔。
示例代码:
public class MathConstants {
// PI 是一个静态常量
public static final double PI = 3.14159;
// MAX_VALUE 是一个静态常量
public static final int MAX_VALUE = 100;
public static void main(String[] args) {
System.out.println("PI is: " + MathConstants.PI);
System.out.println("MAX_VALUE is: " + MathConstants.MAX_VALUE);
// MathConstants.PI = 4.0; // 编译错误!无法为 final 变量 PI 指定值
}
}
关键点:
- 初始化时机:
static final变量必须在类加载时或静态初始化块中进行初始化。 - 全局唯一:它在整个应用程序的生命周期中都是不变的,是全局共享的。
final 修饰参数
final 也可以用来修饰方法的参数,这表示在方法内部,你不能修改这个参数的引用(对于基本类型,就是不能修改其值)。
示例代码:
public class FinalParameterExample {
public void processValue(final int value) {
// value = 20; // 编译错误!不能修改 final 参数的值
System.out.println("Processed value: " + value);
}
public void processObject(final Person person) {
// person = new Person("Eve"); // 编译错误!不能修改 final 参数的引用
person.name = "Eve Updated"; // 合法!可以修改对象内部状态
System.out.println("Processed person's name: " + person.name);
}
public static void main(String[] args) {
FinalParameterExample example = new FinalParameterExample();
example.processValue(10);
Person p = new Person("Frank");
example.processObject(p);
}
}
用途:
- 这是一种安全措施,防止方法内部意外地修改传入的参数。
- 在匿名内部类中,如果需要访问外部方法的局部变量,该局部变量也必须被声明为
final(在 Java 8+ 中,如果变量值未被修改,final是隐式的)。
总结表格
| 修饰符 | 变量类型 | 含义 | 初始化时机 | 示例 |
|---|---|---|---|---|
final |
基本类型 | 值不可变 | 声明时或构造方法中 | final int speed = 60; |
final |
引用类型 | 引用不可变(不能指向新对象),但对象可变 | 声明时或构造方法中 | final List<String> names = new ArrayList<>(); |
static final |
任意类型 | 静态常量,全局共享,值不可变 | 类加载时或静态初始化块中 | public static final String APP_NAME = "MyApp"; |
final |
方法参数 | 在方法内部,参数的引用/值不可变 | 调用方法时传入 | public void doSomething(final int id) |
为什么使用 final?
- 安全性与不变性:创建不可变对象是编程中一个非常重要的设计原则。
final是实现不可变性的关键。String类就是final的,它的内部value数组也是final的,保证了String对象一旦创建,内容就永远不会变,这极大地增强了代码的安全性,特别是在多线程环境下。 - 性能优化:JVM 和编译器可以对
final变量进行优化。final变量可以被内联(inline),避免额外的内存访问。 - 提高代码可读性:看到
final关键字,开发者立刻明白这个变量是一个常量,不应该被修改,使代码意图更清晰。 - 线程安全:不可变对象天生就是线程安全的,因为它们的状态在创建后就不会改变,不需要额外的同步机制。
希望这个详细的解释能帮助你完全理解 Java 中 final 修饰的变量!
