方法参数上的 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
我有以下 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