【Java】一文吃透 final 关键字:从变量到类的全方位解析
作为 Java 中最基础也最常用的关键字之一,final 看似简单,实则藏着不少容易踩坑的细节。无论是面试中高频出现的 “final 修饰的变量真的不可变吗?”,还是实际开发中常量定义、方法防重写等场景,都离不开对 final 的深入理解。今天就带大家从头到尾吃透 final 关键字,结合代码示例拆解它的核心用法和注意事项~
一、final 变量:一旦赋值,终身不变?
1. 核心定义
被 final 修饰的变量,本质是 “不能被重新赋值”,而非 “变量指向的对象不可变”—— 这是很多初学者容易混淆的点,后面会通过代码验证。
2. 三大使用场景与代码示例
(1)成员变量
final 修饰的成员变量(包括实例变量和类变量)必须完成初始化,否则编译器直接报错。初始化方式有两种:声明时直接赋值,或在构造方法中赋值。
public class FinalMemberDemo {
// 声明时直接初始化
private final String INSTANCE_CONSTANT = "实例常量";
// 类变量(static + final 组合,即常量,命名规范:全大写+下划线分隔)
public static final String GLOBAL_CONSTANT = "全局常量";
private final int dynamicFinal;
// 构造方法中初始化 final 成员变量
public FinalMemberDemo(int value) {
this.dynamicFinal = value; // 合法:构造方法中完成初始化
}
public void modifyFinal() {
// INSTANCE_CONSTANT = "修改失败"; // 编译报错:无法为最终变量赋值
// dynamicFinal = 100; // 编译报错:已在构造方法中初始化,不能重复赋值
}
}
(2)局部变量
局部变量被 final 修饰后,同样不能重新赋值,但可以不立即初始化,只要在使用前完成赋值即可。
public class FinalLocalDemo {
public static void calculate(final int num) {
// num = 20; // 编译报错:final 参数不能修改
int result = num * 10;
System.out.println("结果:" + result);
}
public static void main(String[] args) {
final String name;
name = "云扬"; // 合法:使用前初始化
// name = "YunYang"; // 编译报错:不能重新赋值
calculate(5); // 传入参数后,方法内无法修改该参数
}
}
(3)final 与对象引用
重点注意:final 修饰对象时,限制的是 “引用不能指向新对象”,而非 “对象内部属性不可变”!
import java.util.ArrayList;
import java.util.List;
public class FinalObjectDemo {
public static void main(String[] args) {
final List<String> list = new ArrayList<>();
// 合法:对象内部属性可以修改
list.add("Java");
list.add("MySQL");
System.out.println(list); // 输出:[Java, MySQL]
// list = new ArrayList报错:不能重新指向新对象
// list = null; // 编译报错:同样不允许修改引用
}
}
二、final 方法:拒绝被重写的 “倔强方法”
1. 核心定义
被 final 修饰的方法,子类无法重写(Override)。这在设计类时非常有用 —— 如果某些方法的逻辑是核心逻辑,不允许子类篡改,就可以用 final 保护。
2. 代码示例与实战场景
// 父类:定义 final 方法
class Parent {
// final 方法:子类不能重写
public final void showInfo() {
System.out.println("父类核心方法,不可修改");
}
// 普通方法:子类可以重写
public void sayHello() {
System.out.println("父类 Hello");
}
}
// 子类继承父类
class Child extends Parent {
// @Override // 编译报错:Cannot override the final method from Parent
// public void showInfo() {
// System.out.println("尝试重写 final 方法");
// }
// 合法:重写普通方法
@Override
public void sayHello() {
System.out.println("子类 Hello");
}
}
public class FinalMethodDemo {
public static void main(String[] args) {
Child child = new Child();
child.showInfo(); // 输出:父类核心方法,不可修改(调用父类 final 方法)
child.sayHello(); // 输出:子类 Hello(调用重写后的普通方法)
}
}
3. 经典案例:Thread 类的 isAlive () 方法
JDK 中的 Thread 类就是很好的例子:Thread 类本身不是 final(允许子类继承),但它的 isAlive() 方法被声明为 final:
public class Thread implements Runnable {
// native 方法:由操作系统实现,重写无意义,故用 final 禁止重写
public final native boolean isAlive();
// ... 其他方法
}
因为 isAlive() 是本地方法,逻辑依赖操作系统底层实现,子类重写后无法保证正确性,所以用 final 限制重写,这也是设计模式中 “保护核心逻辑” 的常用思路。
三、final 类:不能被继承的 “终极类”
1. 核心定义
被 final 修饰的类,无法被任何类继承(相当于 “断绝后代”)。
2. 代码示例与经典案例
// final 类:不能被继承
final class FinalClass {
public void doSomething() {
System.out.println("final 类的方法");
}
}
// class SubClass extends FinalClass { // 编译报错:Cannot extend final class 'FinalClass'
// }
public class FinalClassDemo {
public static void main(String[] args) {
FinalClass finalObj = new FinalClass();
finalObj.doSomething(); // 输出:final 类的方法
}
}
3. 为什么 String 类是 final 的?
JDK 中的 String 类是典型的 final 类,原因有三个核心:
- 字符串常量池优化:如果 String 可继承,子类可能修改其
value数组(存储字符串的底层结构),导致常量池中的字符串被篡改,破坏常量池机制; - 线程安全:String 作为不可变对象(本质是
final char[] value+ 无修改方法),被final修饰后避免了子类重写修改方法,保证多线程环境下的安全性; - HashCode 不可变性:String 的
hashCode()基于value数组计算,若 String 可继承,子类修改value会导致 hashCode 变化,破坏 HashMap 等集合的哈希表结构。
4. 关键注意点
final类的所有方法默认是final的(但成员变量不一定是final的);- 对比两个场景:
final class A:A 不能被继承,所有方法都不能重写;class B { public final void m1(); public final void m2(); }:B 可以被继承,子类可新增方法,但不能重写 m1 () 和 m2 ();
final类的对象不是 “不可变对象”:除非类的所有成员变量都是final且无修改方法,否则对象属性仍可修改(比如 final 类中定义非 final 的成员变量)。
四、常见面试题与避坑总结
- final、finally、finalize 的区别?
- final:修饰变量(不可重赋值)、方法(不可重写)、类(不可继承);
- finally:try-catch-finally 中的语句块,无论是否抛出异常都会执行(除非 JVM 退出);
- finalize:Object 类的方法,垃圾回收前调用,不推荐使用(无法保证执行时机)。
- final 变量一定是编译期常量吗?不一定!只有
static final修饰且声明时直接赋值的变量才是编译期常量(比如public static final int NUM = 10);而实例变量被 final 修饰时,若在构造方法中赋值,属于运行期常量(值由构造方法参数决定)。 - 为什么不建议滥用 final 类?final 类无法被继承,若后续发现类中的方法存在 Bug,无法通过子类重写修复,只能修改原类代码,违背了 “开闭原则”。只有在确保类的逻辑绝对稳定、不需要扩展时,才适合设计为 final 类。
结语
final 关键字虽然简单,但涵盖了变量、方法、类三个层面的核心用法,理解其 “不可变” 的本质(是引用不可变,而非对象不可变)是关键。在实际开发中,合理使用 final 不仅能提高代码安全性(防止意外修改),还能让代码逻辑更清晰 —— 比如用 static final 定义常量,用 final 方法保护核心逻辑。希望这篇文章能帮大家彻底搞懂 final 关键字,避开常见坑~
如果有疑问或补充,欢迎在评论区留言交流!也可以关注我的「后端技术专栏」,后续会分享更多 Java 核心知识点~



