方法参数上的 final 关键字是否被编译为字节码?

Does final keyword on method parameter get compiled to bytecode?

我有以下 class:

public class Test {
  private int a;

  public Test(final int a) {
    this.a = a;
  }
}

我尝试在 a 上使用和不使用 final 关键字进行编译(使用 javac Test.java)。在这两种情况下,当我反编译(使用javap -v -c Test.class)时,我得到以下字节码(java 11,打开jdk):

Classfile /home/sadeep/repos/podium.cubs-cnt-svc-modelruntime/src/main/java/podium/cubs/cnt/svc/modelruntime/Test.class
  Last modified Nov 3, 2021; size 261 bytes
  MD5 checksum aa55c5cbaad8a25b1ebf28a572fa72e3
  Compiled from "Test.java"
public class podium.cubs.cnt.svc.modelruntime.Test
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #3                          // podium/cubs/cnt/svc/modelruntime/Test
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 1, attributes: 1
Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#14         // podium/cubs/cnt/svc/modelruntime/Test.a:I
   #3 = Class              #15            // podium/cubs/cnt/svc/modelruntime/Test
   #4 = Class              #16            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               (I)V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               SourceFile
  #12 = Utf8               Test.java
  #13 = NameAndType        #7:#17         // "<init>":()V
  #14 = NameAndType        #5:#6          // a:I
  #15 = Utf8               podium/cubs/cnt/svc/modelruntime/Test
  #16 = Utf8               java/lang/Object
  #17 = Utf8               ()V
{
  public podium.cubs.cnt.svc.modelruntime.Test(int);
    descriptor: (I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 6: 0
        line 7: 4
        line 8: 9
}
SourceFile: "Test.java"

方法参数上的 final 不是 jvm 构造吗?

编辑:

javap -p -c -v:

Classfile /home/sadeep/Test.class
  Last modified Nov 3, 2021; size 228 bytes
  MD5 checksum 2f3a79a3c91a62a2d7831651be24168b
  Compiled from "test.java"
class Test
  minor version: 0
  major version: 55
  flags: (0x0020) ACC_SUPER
  this_class: #3                          // Test
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 1, attributes: 1
Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#14         // Test.a:I
   #3 = Class              #15            // Test
   #4 = Class              #16            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               (I)V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               SourceFile
  #12 = Utf8               test.java
  #13 = NameAndType        #7:#17         // "<init>":()V
  #14 = NameAndType        #5:#6          // a:I
  #15 = Utf8               Test
  #16 = Utf8               java/lang/Object
  #17 = Utf8               ()V
{
  private final int a;
    descriptor: I
    flags: (0x0012) ACC_PRIVATE, ACC_FINAL

  public Test(int);
    descriptor: (I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
}
SourceFile: "test.java"

看起来即使使用-p,最终指示符也只为该字段设置。

MethodParameters 属性用于指示参数是最终的。 https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html#jvms-4.7.24

为了javac添加这个属性,需要传递-parameters选项。

TL;DR:字节码中不需要该信息

参数上的 final 关键字意味着您不能在方法或构造函数中为该参数赋值。检查您是否没有这样做是编译器的工作(如果您这样做,编译器将发出一条错误消息)。一旦编译器完成了它的工作,我们就可以确信参数确实始终保持其初始值。字节码中不需要有声明参数final的信息。这就是为什么信息不存在的原因。

来自 Link182 的评论:

Then how do we know the parameter is final in a compiled library?

你不知道。你不需要。你会用这些信息做什么?它只与方法的实现者有关。

虽然 final 关键字可能不会被编译到字节码中,但它确实启用了主要的编译器优化。借用 Strmecki (2021),考虑连接 2 个字符串的方法的两个版本:

版本 1 使用 final 关键字

public static String concatFinalStrings() {
    final String x = "x";
    final String y = "y";
    return x + y;
}

没有 final 关键字的版本 2

public static String concatNonFinalStrings() {
    String x = "x";
    String y = "y";
    return x + y;
}

Strmecki 获得了以下基准测试结果:

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

版本 1 编译生成此字节码

LDC "xy"
ARETURN

版本 2 编译生成此字节码

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

显然,当您告诉编译器“此变量不会更改”时,编译器能够找到非常有效的优化。


Strmecki, D. (2021)。 Java final 关键字 – 对性能的影响。 Baeldung.Com。 https://www.baeldung.com/java-final-performance