final修饰类禁止继承,但抽象类不可用final;修饰方法禁止重写但允许重载;修饰变量仅保证引用不变,对象状态仍可变;仅static final基本类型或字符串且编译期确定才内联。

final 修饰类:禁止继承,但要注意抽象类不能被 final 修饰
当你把一个类声明为 final,JVM 就不允许任何类去继承它。这常用于工具类(比如 java.lang.String)或安全敏感的类,防止子类篡改行为。
- 常见错误:给
abstract类加final—— 编译直接报错Illegal combination of modifiers: 'abstract' and 'final',因为抽象类本意就是被继承的 - 使用场景:写完一个稳定、无扩展需求的工具类(如
StringUtils),又担心别人误继承导致逻辑混乱,就加final - 性能影响几乎为零,纯编译期约束;但会降低框架集成灵活性(比如 Spring AOP 的 CGLIB 代理就无法对
final类生效)
final 修饰方法:锁定实现,但不影响重载
final 方法不能被子类重写,但可以被重载(overload)。它和 @Override 是反向关系:一个说“你别动我”,一个说“我正覆盖你”。
- 常见错误:以为
final方法能阻止反射调用 —— 实际上AccessibleObject.setAccessible(true)依然能绕过访问控制(除非启用了安全管理器) - 使用场景:父类中定义了关键流程骨架(比如模板方法模式里的钩子方法),但某个步骤逻辑绝对不可变,就用
final锁住 - 注意:
private方法隐式是final的,再加final关键字虽不报错,但属于冗余,IDE 通常会警告
final 修饰变量:只赋值一次,但“不变”仅限引用地址
final 变量必须在声明时、构造器中或初始化块里完成赋值,之后不能再指向新对象。但它指向的对象内部状态仍可变 —— 这点最容易误解。
- 常见错误:写
final List<string> list = new ArrayList(); list.add("a");</string>后以为“list 不可变”,其实只是不能list = new ArrayList();,但add/clear都合法 - 使用场景:配置项、常量、依赖注入后的不可变引用(比如 Spring 中
@Autowired private final Service service;) - 参数差异:
final形参只是告诉调用方“我在方法内不会重新赋值这个参数变量”,不影响实参本身,也不影响传入对象的状态 - 如果真要不可变容器,请用
Collections.unmodifiableList()或ImmutableList.of(),而不是只靠final
final 和编译期常量:只有 static final 基本类型/字符串才可能内联
只有同时满足 static + final + 基本类型或 String + 编译期可确定值,才会被编译器当作“编译期常量”,在调用处直接替换为字面量。
- 常见错误:写
static final int TIMEOUT = System.currentTimeMillis() > 0 ? 5000 : 3000;—— 因为不是编译期常量,不会内联,且每次运行值可能不同 - 影响:如果 A 类引用了 B 类的编译期常量,B 类修改该值后只重新编译 B,A 类不重新编译就会沿用旧值(因为 A 里已被替换成字面量)
- 安全做法:不确定是否编译期可算出的值,统一用
static final+ 显式初始化块或私有静态方法封装,避免意外内联
最常被忽略的是 final 对象内容可变这件事——很多人以为加了 final 就万事大吉,结果在多线程里共享了一个 final List,还往里 add 元素,既没同步也没用不可变容器,问题就藏得特别深。









