同学F 写道
在java中的数组访问,举个例子,对于数组int[][][] arry = new int[2][3][4],我从字节码上看,虚拟机对某个arry中的某个元素如arry[1][1][3]的访问,似乎是先获取arry[1]的引用,然后再获取arry[1][1]的引用,再获取数据arry[1][1][3],如果这个过程我没有理解错的话,那么虚拟机是不是对这些“中间引用”(arry[1]、arry[1][1]之类的)创建相应的类型,否则单凭这些引用如何进行数组下表的越界校验?
Java和JVM里本来就没有所谓的“矩形数组”的概念,多维数组只有“数组的数组”(array-of-arrays)或者叫jagged array。
与之对比,C#和CLI里就有真正的多维“矩形”数组,也支持“数组的数组”。关于几种不同的语言里多维数组的差异,可以参考 以前一帖。
也就是说,在Java里
类型 | 说明 |
int | 这是一个基本类型 |
int[] | 这是以int为元素的数组类型 |
int[][] | 这是以int[]为元素的数组类型 |
int[][][] | 这是以int[][]为元素的数组类型 |
一个数组类型的“组件类型”( component type)就是该数组类型的维度(dimension)减去1的类型;字面上看也就是少一对[]。
每个维度上都是一个真正的数组对象。每个数组对象都记录着自己的长度(length)。所以对每个数组对象都可以用arraylength指令去查询它们的长度,每个Xaload / Xastore也就可以做相应的边界检查。
int[][][] array = new int[2][3][4];
这只是个简写而已。虽然这个语句的右手边对应与一组JVM字节码指令,
0: iconst_2 1: iconst_3 2: iconst_4 3: multianewarray #2, 3; //class "[[[I"
实际上它的作用 大致等同于:
int[][][] a = new int[2][][]; for (int i = 0; i < a.length; i++) { a[i] = new int[3][]; for (int j = 0; j < a[i].length; j++) { a[i][j] = new int[4]; } }
以32位HotSpot VM的实现为例,上面两个版本的代码创建出来的对象都是这种样子的:
这样就好理解了吧?
可以参考JVM规范去了解multianewarray的语义:
http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc9.html#multianewarray
HotSpot VM的实现里,参考解释器版的实现比较直观,在这里:
interpreterRuntime.cpp: InterpreterRuntime::multianewarray
objArrayKlass.cpp: objArrayKlass::multi_allocate
typeArrayKlass.cpp: typeArrayKlass::multi_allocate
该指令接受两个参数,
第一个是多维数组的类型,这个例子是里[[[I,也就是int[][][];
第二个是多维数组的维度n,这个例子里n=3。
知道了维度之后,JVM执行这条指令时就会从操作数栈顶弹出n个值(必须都是int型),并以这些int为每个维度的length嵌套循环的去创建数组对象出来;这个例子里也就是把前面iconst_2、iconst_3、iconst_4指令压到操作数栈上的常量2、3、4分别弹出来,并且作为数组的各维度的长度去创建实例。
multianewarray与上面写的那种显式用嵌套循环来初始化多维数组的Java代码最大的差异是,multianewarray会检查所有维度上的length是否非负,如果有负数就会抛 NegativeArraySizeException;要注意的是 无论传入的多维数组是否有维度的长度是0,所有维度都会被检查。
而显然,如果显式用嵌套循环来初始化的话,负数长度的问题就有可能“逃过去”。
看例子:
public class Foo { public static void main(String[] args) { int[][][] array = new int[1][0][-1]; } }
这个会抛NegativeArraySizeException异常:
$ java Foo Exception in thread "main" java.lang.NegativeArraySizeException at Foo.main(Foo.java:3)
而
public class Bar { public static void main(String[] args) { int[][][] a = new int[1][][]; for (int i = 0; i < a.length; i++) { a[i] = new int[0][]; for (int j = 0; j < a[i].length; j++) { // a[i].length == 0 a[i][j] = new int[-1]; // 这个循环体不会被执行,-1的长度就被“跳过去”了 } } } }
这个不会抛异常。