class Example1d {
static final int angle = 35;
static final int length = angle * 2;
}
When the Example1d class is loaded by a Java Virtual Machine, angle and length are not stored as class variables in the method area
Java语言(由Java语言规范所规定的)有常量表达式的概念。这里所认可的常量很重要的一个方面就是它们都是值语义的。值语义意味着你只关心某个数据的值,不关心它的“身份”(identity);也就是说无论存在哪里、有多少份拷贝、是不是同一份拷贝都好,只要值相等你就会认为它们是“相同”的。
那么回过头来看些例子。留意声明的一侧跟使用的一侧不一定是对等的。
如果有:
- public class Foo {
- public static final int BAR = 1;
- public static final int BAZ = 42;
- public static final int GOO = 256;
- public static final int QUUX = 65536;
- }
public class Foo {
public static final int BAR = 1;
public static final int BAZ = 42;
public static final int GOO = 256;
public static final int QUUX = 65536;
}
(留意这是声明的一侧)
那么随便找个别的方法来看这些常量要访问的话会用怎样的字节码。
(接下来是使用的一侧)
- int i = Foo.BAR;
int i = Foo.BAR;
这个语句的右手边的常量表达式会变成:
- iconst_1
iconst_1
常量1就嵌在指令里了,而且不是作为指令的参数,而是指令本身就嵌着“1”这个常量值
- int i = Foo.BAZ;
int i = Foo.BAZ;
这个语句的右手边的常量表达式会变成:
- bipush 42
bipush 42
常量42作为指令bipush的参数嵌在字节码里
- int i = Foo.GOO;
int i = Foo.GOO;
这个语句的右手边的常量表达式会变成:
- sipush 256
sipush 256
常量256作为参数嵌在字节码里
- int i = Foo.QUUX;
int i = Foo.QUUX;
这个语句的右手边的常量表达式会变成:
- ldc #2
ldc #2
常量65536存在常量池里的第2项(这个常量池项的编号是我随便选的,无所谓)。
所以你会发现正常(非反射)去访问这些常量的时候,它们可能的形态就有好多种;其中小常量是尽量不放在常量池里的。
当然,因为字节码概念上会存在方法区里,所以那些嵌在指令里的常量概念上也存在方法区里了。不过跟许多人想像的形式大概不一样吧。
而为了支持反射,同样的一份值会在类的元数据里也放上一份。对应到Class文件格式那就是Field里的 ConstantValue属性,具体的值会存在 声明该常量的类的常量池里(跟使用的一侧没关系)。
- ConstantValue_attribute {
- u2 attribute_name_index;
- u4 attribute_length;
- u2 constantvalue_index;
- }
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
(留意到constantvalue_index存的是个索引号,真正的常量值在这个索引号对应的常量池项)
这一份在运行时就会存在方法区里。
“不是只存在于声明常量的类的元数据里”。