目录
简介
- foreach 的语法糖实现
- for和foreach 遍历的坑
为什么要结合java汇编分析?
因为 java 可以有语法糖,但是汇编绝对不会有语法糖,拨开语法糖的面纱,去看看他们的真面目
for
源码
public class For {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("Brown");
for (int index = 0;index<strings.size();index++){
System.out.println(strings.get(index));
}
}
}
idea反编译
public class For {
public For() {
}
public static void main(String[] args) {
List<String> strings = new ArrayList();
strings.add("Brown");
for(int index = 0; index < strings.size(); ++index) {
System.out.println((String)strings.get(index));
}
}
}
未能激起丝毫的变化
javap反编译
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String Brown
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: iconst_0
18: istore_2
19: iload_2
20: aload_1
21: invokeinterface #6, 1 // InterfaceMethod java/util/List.size:()I
26: if_icmpge 51
29: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_1
33: iload_2
34: invokeinterface #8, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
39: checkcast #9 // class java/lang/String
42: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: iinc 2, 1
48: goto 19
51: return
}
逐字逐句翻译为java 代码
List<String> strings = new ArrayList<>();//0-4
String str = "Brown";//8-9
strings.add(str);//7,11-16
int i = 0; //17-18
if(i>= strings.size()){//19-26
goto return // 跳到方法结束
}
PrintStream pw = System.out;//29-32
Object obj = strings.get(i);//33-34
String str = (String)obj;//39 checkcast 由强转产生
pw.println(str);//42
i++;//45
goto if(i>= strings.size())//48 跳转到 i和size 判断的地方
return // 可以理解为结尾的一个大括号,跳出方法体
由于压根没有goto的语法,所以得翻译成符合java语言规范的代码:
List<String> strings = new ArrayList<>();
strings.add("Brown");
int i = 0;
for(;i<string.size();){
System.out.println((String)strings.get(i));
i++;
}
这里可以发现,每次调用 i< strings.size() 的时候. List的size 方法都会被调用一次
foreach
源码
例子:
public class Foreach {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("Brown");
for (String name:strings){
System.out.println(name);
}
}
}
此时,控制台输出: Brown
反编译
idea反编译
用 idea 自带的反编译
public class Foreach {
public Foreach() {
}
public static void main(String[] args) {
List<String> strings = new ArrayList();
strings.add("Brown");
Iterator var2 = strings.iterator();
while(var2.hasNext()) {
String name = (String)var2.next();
System.out.println(name);
}
}
}
非常智能的分析出来了,foreach 编译为字节码之后,实际就是调用Iterator进行遍历.
javap反编译
javap -v com/aya/Foreach.class
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String Brown
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: invokeinterface #6, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
23: astore_2
24: aload_2
25: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
30: ifeq 53
33: aload_2
34: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
39: checkcast #9 // class java/lang/String
42: astore_3
43: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_3
47: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
50: goto 24
53: return
}
从字节码的层面来看,foreach 就是一个while
逐字逐句翻译伪代码如下:
List<String> strings = new ArrayList<>();//0-4, 通过LocalVariableTable 拿到类型和变量名,类型通过LocalVariableTypeTable 获得
strings.add("Brown");//8-16
Iterator iterator = strings.iterator();//17-18
Boolean has = iterator.hasNext() //23-25
if(has){//30 ifeq 栈顶为0 就跳到结束
String next = (String)iterator.next(); // 33-39 , checkcase在强转时产生这个字节码
System.out.println(next);//42-47
goto has = iterator.hasNext() //50 由于java 没有goto 语法,所以if判断应该改为while
}
return;
整理伪代码逻辑:
List<String> strings = new ArrayList<>();
strings.add("Brown");
Iterator iterator = strings.iterator();
while(iterator.hasNext()){
System.out.println((String)iterator.next());
}
idea 分析还是更加智能一些,在不是必要的情况下,用idea 反编译就可以.
这里可以发现,每次遍历的时候,都是调用Iterator 的hasNext和next 方法, iterator只调用了一次
最佳实践
for和foreach 遍历的坑
foreach循环
public List<String> getList(){
ArrayList<String> strings = new ArrayList<String>(){
@Override
public Iterator<String> iterator() {
System.out.println("iterator 被调用");
return super.iterator();
}
};
strings.add("Brown");
strings.add("Carl");
return strings;
}
@Test
public void testForEach (){
for (String name:getList()){
System.out.println(name);
}
}
控制台依次输出:
1. iterator 被调用
2. Brown
3. Carl
for循环
public List<String> getList(){
System.out.println("getList 被调用");
ArrayList<String> strings = new ArrayList<String>();
strings.add("Brown");
strings.add("Carl");
return strings;
}
@Test
public void testForEach (){
for (int i = 0;i<getList().size();i++){
System.out.println(getList().get(i));
}
}
控制台输出:
1. getList 被调用
2. getList 被调用
3. Brown
4. getList 被调用
5. getList 被调用
6. Carl
7. getList 被调用
for循环修复
所以千万不要在for 循环时做方法调用哦.可以写成如下的形式:
@Test
public void testForEachFix(){
List<String> list = getList();
for (String name: list){
System.out.println(name);
}
}
此时控制台输出:
1. getList 被调用
2. Brown
3. Carl
如果你已经理解字节码汇编指令
分析for和foreach 的特性后. 这个问题是不是已经不再疑惑了呢?
Date
开始日期小于等于结束日期
@Test
public void testLessEqualDate (){
Date now = new Date();
Date start = new Date(now.getTime());
Date end = new Date(now.getTime());
end.setDate(end.getDate()+2);
for (;start.compareTo(end)<=0;start.setDate(start.getDate()+1)){
System.out.println(start);
}
}
开始日期小于结束日期
@Test
public void testLessDate (){
Date now = new Date();
Date start = new Date(now.getTime());
Date end = new Date(now.getTime());
end.setDate(end.getDate()+2);;
for (;start.before(end);start.setDate(start.getDate()+1)){
System.out.println(start);
}
}