概述
final在java中作为不可变语义的修饰词,接下来我们通过一段示例代码从字节码角度分析以下两点:
- final在类中不同的地方声明是怎么样标识的?
- 为局部变量声明final有意义吗?
示例代码
public final class TestFinal {
private final int a = 10;
public final void testFinalMethod() {
System.out.println("testFinalMethod...");
}
public final void testFianlLocalVar() {
final Object obj = new Object();
}
}
这段代码本身没有什么需要解读的, 我们来关注一下final声明在不同的位置,字节码层面是怎么标识的。
1. 声明final在类定义中
通过final修饰类, 根据虚拟机规范ClassFile结构如下:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
可以看到ClassFile结构中会通过access_flags两个字节的长度来记录访问修饰符。 access_flags取值如下:
借助字节码查看工具可以看到access_flags的值就是public以及final。
2. 声明final在字段定义中
通过final修饰字段,同样查看虚拟机规范Fields的结构标识如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
可以看到也会通过两个字节长度的access_flags来记录访问修饰符。再通过字节码工具查看生成的字节码的值就是private final:
声明final在方法Methods中与之类似,此处不再复述。感兴趣可以自己查看一下。
3. 声明final在局部变量中
在方法中将一个局部变量声明成final时,虚拟机规范描述方法中的字节码是存在code属性集中,并且通过局部变量表来记录局部变量信息。
我们查看一下虚拟机的局部变量表结构信息:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}
通过局部变量表结构可以看到,虚拟机层面根本没有access_flags这个属性。
通过字节码工具查看实际的局部变量表中也是如此:
由此可以证明: 在虚拟机层面,将局部变量声明为final是没有意义的。
为局部变量声明final有意义吗?
回答这个问题个人的理解应该从下面两个维度来阐述:
- 从虚拟机层面来说,局部变量声明final是没有意义的,生成的字节码根本不会记录。
- 从java语言层面来说是有意义的,如我们通过内部类访问外部对象需要通过final修饰,另外在做代码重构时,将对象临时修饰为final来判断对象的作用域以及是否变化等情况也是有意义的。
总结
在日常开发中,经常看见项目中的代码很多时候都不假思索的将局部变量修饰为final,可能是对final的一种误解。理解其本质才能更好的使用。