对应 class 文件中的位置如下图所示。
一个类中定义的字段会被存储在字段表(fields)中,包括静态的和非静态的。
来看这样一段代码。
public class FieldsTest {
private String name;
}
字段只有一个,修饰符为 private,类型为 String,字段名为 name。可以用下面的伪代码来表示 field 的结构。
field_info {
u2 access_flag;
u2 name_index;
u2 description_index;
}
1)对于基本数据类型来说,使用一个字符来表示,比如说 I 对应的是 int,B 对应的是 byte。
2)对于引用数据类型来说,使用 L***;
的方式来表示,L
开头,;
结束,比如字符串类型为 Ljava/lang/String;
。
3)对于数组来说,会用一个前置的 [
来表示,比如说字符串数组为 [Ljava/lang/String;
。
对应到 class 文件中的位置如下图所示。
看到这里相信你就能明白经常在 javap 命令中看到的一些奇怪的字符的意思了。
方法表和字段表类似,区别是用来存储方法的信息,包括方法名,方法的参数,方法的签名。
就拿 main 方法来说吧。
public class MethodsTest {
public static void main(String[] args) {
}
}
先用 jclasslib 看一下大概的信息。
对应到 class 文件中的位置如下图所示。
属性表是 class 文件中的最后一部分,通常出现在字段和方法中。
来看这样一段代码。
public class AttributeTest {
public static final int DEFAULT_SIZE = 128;
}
只有一个常量 DEFAULT_SIZE,它属于字段中的一种,就是加了 final 的静态变量。先通过 jclasslib 看一下它当中一个很重要的属性——ConstantValue,用来表示静态变量的初始值。
我画了一副图,可以完整的表示字段的结构,包含属性表在内。
对应到 class 文件中的位置如下图所示。
来看下面这段代码。
public class MethodCode {
public static void main(String[] args) {
foo();
}
private static void foo() {
}
}
main 方法中调用了 foo 方法。通过 jclasslib 看一下它当中一个很重要的属性——Code, 方法的关键信息都存储在里面。
对应 class 文件中的位置如下图所示。
评论区有读者问到:“怎么通过索引值,定位到在class 文件中的位置,这个是咋算的?”
在Java类文件中,常量池是一个索引表,它从索引值1开始计数,每个条目都有一个唯一的索引。
定位过程大致如下:
可以抽象成一个数组和一个 for 循环,就能明白了。
int[] constantPool = new int[constantPoolCount];
for (int i = 1; i < constantPoolCount; i++) {
int tag = constantPool[i];
switch (tag) {
case CONSTANT_Integer_info:
i += 4;
break;
case CONSTANT_Float_info:
i += 4;
break;
case CONSTANT_Long_info:
i += 8;
break;
case CONSTANT_Double_info:
i += 8;
break;
case CONSTANT_Utf8_info:
int length = constantPool[i + 1];
i += length + 1;
break;
case CONSTANT_String_info:
i += 2;
break;
case CONSTANT_Class_info:
i += 2;
break;
case CONSTANT_Fieldref_info:
i += 4;
break;
case CONSTANT_Methodref_info:
i += 4;
break;
case CONSTANT_InterfaceMethodref_info:
i += 4;
break;
case CONSTANT_NameAndType_info:
i += 4;
break;
case CONSTANT_MethodHandle_info:
i += 3;
break;
case CONSTANT_MethodType_info:
i += 2;
break;
case CONSTANT_InvokeDynamic_info:
i += 4;
break;
default:
throw new RuntimeException("Unknown tag: " + tag);
}
}
到此为止,class 文件的内部算是剖析得差不多了,希望能对大家有所帮助。第一次拿刀,手有点颤,如果哪里有不足的地方,欢迎大家在评论区毫不留情地指出来!
相信大家看完这篇内容应该能对 class 文件有一个比较清晰的认识了。