Java final 关键字是 Java 中最基础、最重要的修饰符之一,它表示“最终的、不可变的、不可被修改/继承/重写”。
final 可以修饰类、方法、变量(包括成员变量、局部变量、方法参数),每种用法作用完全不同。
下面按使用场景完整拆解,包含规则、代码示例、常见误区、实战场景和面试高频问法。
1. final 修饰变量(最常见用法)
核心含义:只能赋值一次,赋值后不可再改变。
| 类型 | 赋值时机要求 | 能否重新赋值 | 影响对象内容? | 经典例子 |
|---|---|---|---|---|
| 基本类型 | 声明时或初始化块/构造器中 | 否 | — | final int MAX = 100; |
| 引用类型 | 声明时或初始化块/构造器中 | 否(引用) | 可以(对象内容可变) | final List list = new ArrayList<>(); |
| blank final(空白 final) | 必须在构造器或初始化块中赋值 | 否 | 同上 | final String name; |
代码示例
public class FinalVariableDemo {
// 1. 声明时初始化(最常见)
final int MAX_VALUE = 100;
// 2. blank final(空白 final) - 必须在构造器或初始化块中赋值
final String name;
final int age;
// 初始化块(实例初始化块)
{
age = 25;
}
// 构造器赋值
public FinalVariableDemo(String name) {
this.name = name; // 必须赋值一次
// this.name = "other"; // 编译报错:不能重复赋值
}
public void test() {
// MAX_VALUE = 200; // 编译报错:不能重新赋值
final List<String> list = new ArrayList<>();
list.add("A"); // OK:对象内容可变
list.add("B");
// list = new ArrayList<>(); // 编译报错:引用不可变
}
public static void main(String[] args) {
final int local = 10;
// local = 20; // 编译报错
}
}
关键误区(面试必考):
final修饰引用类型,引用不可变,但对象内容可变(除非对象本身是不可变的,如 String)。- 错误理解:很多人以为
final List list就表示列表不可变 → 错!只是 list 变量不能指向别的 List。
2. final 修饰方法
核心含义:子类不能重写(override)该方法。
public class Animal {
// final 方法
public final void eat() {
System.out.println("动物在吃东西...");
}
public void sleep() {
System.out.println("动物在睡觉...");
}
}
public class Dog extends Animal {
// 试图重写 final 方法 → 编译报错
// public void eat() { ... } // Cannot override the final method eat()
@Override
public void sleep() {
System.out.println("狗在睡觉...");
}
}
使用场景(实战):
- 模板方法模式中,核心算法步骤不允许子类修改(只允许子类实现钩子方法)。
- 安全性要求高的工具类方法(如加密算法、校验逻辑)。
- 防止子类破坏父类逻辑。
注意:
- private 方法隐式为 final(因为子类根本看不到)。
- final 方法可以被调用,但不能被 override。
3. final 修饰类
核心含义:该类不能被继承(没有子类)。
public final class String {
// String 就是 final 类
// 无法写:class MyString extends String { }
}
常见 final 类(JDK 源码):
- java.lang.String
- java.lang.Integer / Long / Double 等包装类
- java.lang.System
- java.lang.Math
实战场景:
- 不可变类设计(Immutable Class):String、Integer、LocalDateTime 等。
- 安全类:防止被恶意继承并破坏行为(如 SecurityManager 相关类)。
- 性能优化:JVM 知道没有子类,可做更多激进优化(内联、逃逸分析等)。
- 工具类:如 Collections.unmodifiableList() 返回的类。
代码示例:
public final class Constants {
public static final int MAX_USERS = 1000;
public static final String DEFAULT_ROLE = "GUEST";
private Constants() {} // 防止实例化
}
4. final + static 组合(高频面试)
public class Config {
// 最常见的常量写法
public static final int PAGE_SIZE = 20;
public static final String API_KEY = "abc123";
// 运行时常量(类加载时初始化)
public static final long START_TIME = System.currentTimeMillis();
}
特点:
- static final 基本类型/字符串 → 编译期常量,可内联到使用处。
- static final 引用类型 → 引用不可变,但对象内容可变(除非不可变对象)。
5. final 在方法参数上的使用(较少见但有意义)
public void process(final User user) {
// user = new User(); // 编译报错
user.setName("new name"); // OK
}
作用:防止方法内部意外改变引用指向,增加代码可读性(表达“这个参数我不打算修改引用”)。
6. 面试高频问题 & 答案提炼
| 问题 | 核心回答 |
|---|---|
| final 修饰局部变量和成员变量有何区别? | 局部变量只要在使用前赋值一次即可;成员变量(blank final)必须在构造器或初始化块中赋值 |
| final 变量一定是常量吗? | 不一定。基本类型是常量;引用类型只是引用不变,对象内容可变 |
| 为什么 String 是 final 的? | 安全性(类加载器、反射)、不可变性(HashMap key、线程安全)、缓存(字符串常量池) |
| final 方法和 private 方法区别? | private 隐式 final;final 方法可见但不可重写;private 方法子类不可见 |
| final 能提高性能吗? | 可能。JVM 可做激进内联、优化逃逸分析,但现代 JIT 已很聪明,影响有限 |
| 可以 final + abstract 一起用吗? | 不能,编译报错(抽象方法必须被重写,final 禁止重写) |
7. 总结口诀(背诵版)
- 变量:一次赋值,终身不变(引用不变,内容看情况)
- 方法:子类别想重写我
- 类:不准有儿子(禁止继承)
final 出现频率排序(实战):
- static final 常量
- final 引用类型成员变量(尤其集合)
- final 方法(保护关键逻辑)
- final 类(不可变类、工具类)
希望这份总结让你对 final 彻底清晰!
想看更深入的实战案例(比如设计一个完整的不可变类、final 在多线程中的作用、JVM 对 final 的优化细节等),随时告诉我!