项目地址:
数组在Java虚拟机中是个比较特殊的概念。为什么这么说呢?有下面几个原因:
首先,数组类和普通的类是不同的。普通的类从class文件中加载,但是数组类由Java虚拟机在运行时生成。数组的类名是左方括号([)
+数组元素的类型描述符;数组的类型描述符就是类名本身。例如,int[]
的类名是[I
,int[][]
的类名是[[I
,Object[]
的类名是[Ljava/lang/Object;
,String[][]
的类名是[[java/lang/String;
等等。
其次,创建数组的方式和创建普通对象的方式不同。普通对象由new指令创建,然后由构造函数初始化。基本类型数组由newarray
指令创建;引用类型数组由anewarray
指令创建;另外还有一个专门的multianewarray
指令用于创建多维数组。
最后,数组和普通对象存放的数据也是不同的。普通对象中存放的是实例变量,通过putfield
和getfield
指令存取。数组对象中存放的则是数组元素,通过<t>aload
和<t>astore
系列指令按索引存取。其中<t>可以是a、b、c、d、f、i、l或者s,分别用于存取引用、byte、char、double、float、int、long或short类型的数组。另外,还有一个arraylength
指令,用于获取数组长度。
一般来说,一个 MyClass 实例对应一个 class 文件,但是数组的 Class 是没有对应的 class 文件的。所以我们直接创建一个 MyClass 对象,给它设置一个 className 就好了。
我们实现一下 ANewArray 指令,先看例子:
String[] argss = new String[4];
看看编译后的字节码:
anewarray 后面跟了一个类名 String。表示需要创建String类型的数组,数组的大小由前一条指令给出,也就是4。
看看代码实现:
@Override
public void execute(StackFrame frame) {
int count = frame.getOperandStack().popInt();
if (count < 0) {
throw new MyJvmException("java.lang.NegativeArraySizeException");
}
ConstantPool constantPool = frame.getMyMethod().getMyClass().getConstantPool();
// must be a symbolic reference to a class, array, or interface type.
ConstantPool.Constant constant = constantPool.getConstant(operand);
ClassRef classRef = (ClassRef) constant.value;
MyClass resolvedClass = classRef.getResolvedClass();
MyClass arrayClass = resolvedClass.toArrayClass();
ArrayObject arrayObject = arrayClass.newArrayObject(count);
frame.getOperandStack().pushRef(arrayObject);
}
逻辑很简单,resolvedClass 就是上面例子中的 String 类。我们需要根据这个类名创建出数组的类名,具体的规则就是:
// [XXX -> [[XXX
// int -> [I
// XXX -> [LXXX;
有了类名后,使用 ClassLoader 加载一下,当然 ClassLoader 里面的逻辑也需要特殊处理:
private MyClass loadArrayClass(String name) {
MyClass myClass = MyClass.createArrayClass(name, this);
loadedClasses.put(name, myClass);
return myClass;
}