纵观所有的字节码指令,并没有与for名字相关的指令,那for循环是如何实现的呢?接下来以sum相加求和的例子来看for循环的实现细节,代码如下所示。
public int sum(int[] numbers) {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}上面代码对应的字节码如下。 0: iconst_0 1: istore_2
2: aload_1
3: astore_3
4: aload_3
5: arraylength
6: istore 4
8: iconst_0
9: istore 5
11: iload 5
13: iload 4
15: if_icmpge 35
18: aload_3
19: iload 5
21: iaload
22: istore 6
24: iload_2
25: iload 6
27: iadd
28: istore_229: iinc 5, 1
32: goto 11
35: iload_2
36: ireturn
为了方便理解,这里先把对应的局部变量表的示意图画出来,如图2-14所示。
图2-14
for循环局部变量表示意图下面以numbers数组内容[10,20,30]为例来讲解上述的字节码的执行过程。
第0~1行:把常量0加载到操作数栈上,随后通过istore_2指令将0出栈赋值给局部变量表下标为2的元素,也就是给局部变量sum赋值为0,如图2-15所示。
图2-15
for循环执行细节(1)第2~9行用来初始化循环控制变量,其伪代码如下所示。$array = numbers;
$len = $array.arraylength
i
=
0
第
2
~
3
行
:
a
l
o
a
d
1
指
令
的
作
用
是
加
载
局
部
变
量
表
中
下
标
为
1
的
变
量
(
参
数
n
u
m
b
e
r
s
)
,
a
s
t
o
r
e
3
指
令
的
作
用
是
将
栈
顶
元
素
存
储
到
局
部
变
量
下
标
为
3
的
位
置
上
,
记
为
i = 0 第2~3行:aload_1指令的作用是加载局部变量表中下标为1的变量(参数numbers),astore_3指令的作用是将栈顶元素存储到局部变量下标为3的位置上,记为
i=0第2~3行:aload1指令的作用是加载局部变量表中下标为1的变量(参数numbers),astore3指令的作用是将栈顶元素存储到局部变量下标为3的位置上,记为array,如图2-16所示。
图2-16
for循环执行细节(2)第4~6行:计算数组的长度,astore_3加载
a
r
r
a
y
到
栈
顶
,
调
用
a
r
r
a
y
l
e
n
g
t
h
指
令
获
取
数
组
长
度
存
储
到
栈
顶
,
随
后
调
用
i
s
t
o
r
e
4
将
数
组
长
度
存
储
到
局
部
变
量
表
的
第
4
个
位
置
,
这
个
变
量
是
表
示
数
组
的
长
度
值
,
记
为
array到栈顶,调用arraylength指令获取数组长度存储到栈顶,随后调用istore 4将数组长度存储到局部变量表的第4个位置,这个变量是表示数组的长度值,记为
array到栈顶,调用arraylength指令获取数组长度存储到栈顶,随后调用istore4将数组长度存储到局部变量表的第4个位置,这个变量是表示数组的长度值,记为len,过程如图2-17所示。
第8~9行:初始化数组遍历的下标初始值。iconst_0将0加载到操作数栈上,随后istore 5将栈顶的0存储到局部变量表中的第5个位置,这个局部变量是数组遍历的下标初始值,记为$i,如图2-18所示。11~32行是真正的循环体,详细介绍如下。for循环执行细节
(3)图2-18 for循环执行细节(4)第11~15行的作用是判断循环能否继续。这部分的字节码如下所示。11: iload 5
13: iload 4
15: if_icmpge 35
首先通过iload 5和iload 4指令加载
i
和
i和
i和len到栈顶,然后调用if_icmpge进行比较,如果
i
>
=
i>=
i>=len,直接跳转到第35行指令处,for循环结束;如果
i
<
i<
i<len则继续往下执行循环体,可以用如下伪代码表示。if ($i >=
l
e
n
)
g
o
t
o
35
;
过
程
如
图
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
34
b
03320
d
04240
b
e
a
c
d
8
a
b
a
b
7
c
6
a
5080.
j
p
g
?
x
−
o
s
s
−
p
r
o
c
e
s
s
=
i
m
a
g
e
/
w
a
t
e
r
m
a
r
k
,
t
y
p
e
d
3
F
5
L
X
p
l
b
m
h
l
a
Q
,
s
h
a
d
o
w
5
0
,
t
e
x
t
Q
1
N
E
T
i
B
A
5
L
i
N
54
i
x
5
Y
a
Z
5
p
a
H
56
u
g
,
s
i
z
e
2
0
,
c
o
l
o
r
F
F
F
F
F
F
,
t
7
0
,
g
s
e
,
x
1
6
)
。
第
18
~
22
行
的
作
用
是
把
len) goto 35; 过程如图 ![在这里插入图片描述](https://img-blog.csdnimg.cn/34b03320d04240beacd8abab7c6a5080.jpg?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN54ix5YaZ5paH56ug,size_20,color_FFFFFF,t_70,g_se,x_16) 。第18~22行的作用是把
len)goto35;过程如图![在这里插入图片描述](https://img−blog.csdnimg.cn/34b03320d04240beacd8abab7c6a5080.jpg?x−oss−process=image/watermark,typed3F5LXplbmhlaQ,shadow50,textQ1NETiBA5LiN54ix5YaZ5paH56ug,size20,colorFFFFFF,t70,gse,x16)。第18~22行的作用是把array[
i
]
赋
值
给
n
u
m
b
e
r
。
a
l
o
a
d
3
加
载
i]赋值给number。aload_3加载
i]赋值给number。aload3加载array到栈上,iload 5加载
i
到
栈
上
,
然
后
i
a
l
o
a
d
指
令
把
下
标
为
i到栈上,然后iaload指令把下标为
i到栈上,然后iaload指令把下标为i的数组元素加载到操作数栈上,随后istore 6将栈顶元素存储到局部变量表下标为6的位置上,过程如图2-20所示。
图2-20
for循环执行细节(6)第24~28行:iload_2和iload 6指令把sum和number值加载到操作数栈上,然后执行iadd指令进行整数相加,过程如图2-21所示。
第29行:“iinc 5,1”指令对执行循环后的$i加一。iinc指令比较特殊,之前介绍的指令都是基于操作数栈来实现功能,它则是直接对局部变量进行自增,不用先入栈、执行加一操作,再将结果出栈存储到局部变量表,因此效率非常高,适合循环结构,如图2-22所示。第32行:goto 11指令的作用是跳转到第11行继续进行循环条件的判断。图2-21 for循环执行细节(7)
图2-22
for循环执行细节(8)上述字节码用伪代码表示就是:@start: if ($i >= $len) return;
$item =
a
r
r
a
y
[
array[
array[i];
sum += $item;
++ $i
goto @start
整段代码的逻辑看起来非常熟悉,可以用下面的Java代码表示。int sum = 0;
for (int i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
由此可见,for(item:array)就是一个语法糖,字节码会让它现出原形,回归它的本质。