1. 概述

在 Java 开发圈里,final 关键字是否能带来性能提升一直是一个热门话题。根据使用位置的不同,final 的作用和对性能的影响也会有所不同。

在这篇文章中,我们将深入探讨在代码中使用 final 是否真的能带来性能上的优势。我们会从变量、方法、类三个层面分析其性能影响。

除了性能方面的讨论,我们也会提及 final 在设计层面的意义,并给出推荐的使用场景。

2. 局部变量

final 应用于局部变量时,其值只能被赋值一次

我们可以在声明时赋值,也可以在构造器中赋值。如果后续尝试修改这个变量,编译器会直接报错。

2.1. 性能测试

我们来测试一下,给局部变量加上 final 是否能提升性能。

我们使用 JMH 工具 来测量基准方法的平均执行时间。首先,我们测试不使用 final 的局部变量进行字符串拼接:

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatNonFinalStrings() {
    String x = "x";
    String y = "y";
    return x + y;
}

接着,我们做同样的操作,但是变量加上了 final

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatFinalStrings() {
    final String x = "x";
    final String y = "y";
    return x + y;
}

JMH 会自动运行预热迭代,让 JIT 编译器 的优化生效。下面是测试结果(单位:纳秒):

Benchmark                              Mode  Cnt  Score   Error  Units
BenchmarkRunner.concatFinalStrings     avgt  200  2,976 ± 0,035  ns/op
BenchmarkRunner.concatNonFinalStrings  avgt  200  7,375 ± 0,119  ns/op

在这个例子中,使用 final 的局部变量使执行速度提升了 2.5 倍 ✅。

2.2. 静态优化

字符串拼接的例子很好地说明了:**final 可以帮助编译器进行静态优化**。

对于非 final 的局部变量,编译器生成的字节码如下:

NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN

而加上 final 后,编译器发现这两个字符串是常量,直接优化成了:

LDC "xy"
ARETURN

⚠️ 但需要注意的是,大多数情况下,给局部变量加 final 并不会像这个例子一样带来显著的性能收益。

3. 实例变量和类变量

我们可以将 final 应用于实例变量或类变量(静态变量)。这样可以确保它们的值只能被赋值一次。

实例变量可以在声明时、初始化块中或构造器中赋值。

类变量(静态变量)可以通过添加 static 关键字声明。如果加上 final,就定义了一个常量。常量可以在声明时或静态初始化块中赋值:

static final boolean doX = false;
static final boolean doY = true;

我们写一个简单的方法,使用这些布尔常量:

Console console = System.console();
if (doX) {
    console.writer().println("x");
} else if (doY) {
    console.writer().println("y");
}

接下来我们去掉 final,比较生成的字节码:

  • 非 final 类变量:76 行字节码
  • final 类变量(常量):39 行字节码

✅ 加上 final 后,编译器会直接将常量替换为其实际值,从而减少字节码体积,实现静态优化。

不过,这种优化在真实项目中很少能带来明显性能提升。

4. 实际上是 final(Effectively Final)

Java 8 引入了 effectively final 这个概念。如果一个变量虽然没有显式声明为 final,但在初始化后从未被修改,那么它就是 effectively final。

这个概念主要是为了让 lambda 表达式可以访问外部的局部变量。但 ❌ 编译器并不会像对待真正的 final 变量那样,对其进行静态优化。

5. 类和方法

final 应用于类或方法时,其目的完全不同:

  • final 类:不能被继承
  • final 方法:不能被重写

目前没有证据表明在类或方法上使用 final 会带来性能提升。相反,❌ 滥用 final 会限制代码的可扩展性,影响面向对象设计的灵活性。

当然,有些场景下使用 final 是合理的,比如为了实现不可变对象。但 ❌ 性能优化不是在类或方法上使用 final 的好理由

6. 性能 vs. 清晰设计

除了性能,使用 final 还有其他好处——它可以提高代码的可读性和表达力。下面是几个例子:

  • final 类:表明该类不允许被继承,有助于实现不可变对象
  • final 方法:防止子类重写导致行为不一致
  • final 方法参数:防止副作用
  • final 变量:表明它是只读的

✅ 因此,我们应该 final 当作一种设计意图的表达方式,让其他开发者更容易理解代码的设计逻辑。同时,它也可以作为编译器优化的一个提示。

7. 总结

本文我们分析了 final 关键字的性能影响:

  • ✅ 对变量使用 final 可能会带来轻微的性能优化
  • ❌ 对类和方法使用 final 并不会提升性能
  • ❌ effectively final 变量不会触发编译器的静态优化
  • ✅ 使用 final 更多是为了表达设计意图,提升代码可读性和健壮性

源码地址:GitHub 项目地址


原始标题:The Java final Keyword – Impact on Performance | Baeldung