final修饰变量时锁住的是引用指向而非对象内容;基本类型值不可变,引用类型仅地址不可变,对象状态是否可变取决于类设计。

final 修饰变量时,到底锁住了什么
它锁住的是「引用指向」,不是对象内容。比如 final List<string> list = new ArrayList()</string>,你不能把 list 指向另一个 ArrayList,但完全能调用 list.add("x") 修改内部元素。
常见错误现象:final 声明后仍被意外修改,误以为对象整体不可变;或在多线程中误用 final 当线程安全保证。
- 基本类型(
int、boolean):值不可变,真正意义上的常量 - 引用类型(
String、Map、自定义类):引用地址不可变,对象状态是否可变取决于该类自身设计 - 想真正不可变?优先选
String、LocalDateTime这类天然不可变类;集合类用Collections.unmodifiableList()包一层
static final 和 final 的区别在哪
关键在「生命周期归属」和「初始化时机」:不加 static 的 final 是实例级的,每个对象一份;加了 static final 才是类级常量,且必须在类加载阶段就确定值。
使用场景:配置项、协议码、数学常量(如 Math.PI)必须用 static final;而某个对象内部只初始化一次的缓存字段(如懒加载的 final ObjectMapper),可用非静态 final。
立即学习“Java免费学习笔记(深入)”;
-
static final必须在声明时或静态代码块中赋值,否则编译报错variable xxx might not have been initialized - 非静态
final可在构造方法中赋值,但所有构造路径都必须覆盖到,否则同样报错 - JVM 对
static final基本类型有编译期常量优化:直接内联字面量,可能影响调试和热更新行为
final 参数和 final 方法的实际作用
final 用在参数上,只是防止在方法体内重新赋值给该形参变量;用在方法上,是禁止子类重写——这两者都不影响调用方行为,纯属编码约束。
容易踩的坑:有人以为 final String s 能防止字符串被篡改,其实没意义,因为 String 本身不可变;也有人给 private 方法加 final,属于冗余,JVM 已默认不许重写。
- 参数加
final主要用于明确意图,配合 IDE 提示避免误赋值,对运行时零开销 -
final方法不能被重写,但可以被重载;若父类方法是final,子类同签名方法会编译失败,错误信息是cannot override final method - 接口中不能用
final修饰方法(Java 8+ 默认default或static方法也不允许加final)
final 类为什么不能被继承
这是最彻底的封装控制:从语言层面切断所有继承可能性,强制使用者只能组合或依赖,不能扩展。典型例子是 String、Integer、LocalDateTime。
性能影响极小,但语义重量大。一旦声明为 final,后续任何功能增强都只能靠新增类或静态工具方法,没法靠子类定制。
- 声明
final class后,所有方法自动成为final,无需再显式标注 - 如果类里有
protected成员,final类会让这些成员实质失效(没人能继承访问) - 注意反模式:为“防误用”而把工具类、配置类全标
final,其实没必要;重点应放在业务核心不可变模型上
真正难的是判断「哪里该用 final」而不是「怎么写 final」。比如一个 DTO 字段要不要 final,取决于它是否参与构建逻辑;一个服务类要不要 final,要看它是否设计为组合而非继承的基石。这些没标准答案,得看上下文里谁可能改它、为什么改、改了会破坏什么。









