java for循环的执行顺序和几种常用写法

本文详细解析了C语言中for循环的执行顺序,并通过实例展示了循环变量的变化过程。此外,还介绍了Java中for-each循环的使用方法及其背后的实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算是温习吧。问题比较基础,但是也比较重要。(虽然是C,但是很经典)

for循环可以说在每个程序中都少不了的,语句头包括三个部分:初始化,判读条件,一个表达式。

但是这三个部分的执行顺序是什么,这是我们所关心的,也是本文所要讨论的。

下面先看一段简单的代码。

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. int main()  
  4. {  
  5.     int x, a;  
  6.       
  7.     for (x=0,a=0; a<=1 && !x++; a++)  
  8.     {  
  9.         a++;  
  10.     }  
  11.       
  12.     printf("%d", a);  
  13.     return 0;  
  14. }  

代码很简单,可是打印结果呢?

有的朋友可能会想当然的认为,  打印结果是1.

可是,真的是1吗?   复制到编译器上跑跑,奇迹般的发现,其实是2.

下面就分析下这简单代码。


第一步:初始化定义,x = 0, a = 0;

第二步:a小于1, x 的非为1,符合循环条件

第三步:x++后x自增为1.

第四步:进入循环体,a++,a自增为1.

第五步:执行循环语句头中的a++,a自增为2.

第六步:执行循环语句头的判读条件,a=2>1,故不满足,不执行循环语句。

第七步:打印a的值为2.


怎么样,现在豁然开朗了吧。(如果还是不懂...那就抱歉了。)


从这个例子,我们可以得出如下结论:

执行顺序是:初始化--判断--函数体——然后for循环的第3句--判断——函数体---for循环的第三句--判断……


下面解答几点疑惑:

1、第一次例如初始化i=0,是否还执行后面的i++或++i?

第一次先初始化,然后判断,i++就不执行了

2、第一次完了以后,初始化这句就不用了,是先执行i++还是限制性判断语句呢?

先执行i++

3、现在知道是先执行i++,再执行判断语句,i++不是后++吗,例如i为1,那i++后执行判断语句,i是几呢?

i是2.

4、好像i++和++i在for()里时结果都一样。

都一样,for语句头的执行顺序决定了这两个写法都一样,都是i++或这++i这两句执行完了再进行判断,此时(两种情况)i已经加1了。


分析了这么多,相信大家也都懂了。

留个经典的例子,让大家自己去分析吧。

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. int main( )  
  4. {  
  5.     int i = 0, j = 10;  
  6.     for(i=0; i<j; i++,j--)  
  7.     {  
  8.         if(j==10)  
  9.         {  
  10.             printf("%d,%d ",i,j);  
  11.             j--;  
  12.         }  
  13.         j++;  
  14.     }  
  15.     printf("%d,%d ",i,j);  
  16.     return 0;  
  17. }  

打印结果:

0,10 9,9 

如果不懂的,可以参照下面这个对上述例子逐句打印的例子,应该会有助于理解。

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. int print1(int i,int j)  
  4. {  
  5.     printf("执行判断条件,i=%d,j=%d \n",i,j);  
  6.     return 1;  
  7. }  
  8.   
  9. int main( )  
  10. {  
  11.     int i = 0, j = 10;  
  12.       
  13.     for(i=0;((print1(i,j)) && (i<j));  
  14.         i++,j--,printf("执行for()中的第三个语句,i=%d,j=%d \n",i,j))  
  15.     {  
  16.         if(j==10)  
  17.         {  
  18.             printf("%d,%d ",i,j);  
  19.             j--;  
  20.         }  
  21.         j++;  
  22.         printf("执行for循环体中的最后一句,i=%d,j=%d\n ",i,j);  
  23.     }  
  24.     printf(" %d,%d ",i,j);  
  25.     return 0;  
  26. }  

打印结果:

执行判断条件,i=0,j=10 

0,10 执行for循环体中的最后一句,i=0,j=10

 执行for()中的第三个语句,i=1,j=9 

执行判断条件,i=1,j=9 

执行for循环体中的最后一句,i=1,j=10

 执行for()中的第三个语句,i=2,j=9 

执行判断条件,i=2,j=9 

执行for循环体中的最后一句,i=2,j=10

 执行for()中的第三个语句,i=3,j=9 

执行判断条件,i=3,j=9 

执行for循环体中的最后一句,i=3,j=10

 执行for()中的第三个语句,i=4,j=9 

执行判断条件,i=4,j=9 

执行for循环体中的最后一句,i=4,j=10

 执行for()中的第三个语句,i=5,j=9 

执行判断条件,i=5,j=9 

执行for循环体中的最后一句,i=5,j=10

 执行for()中的第三个语句,i=6,j=9 

执行判断条件,i=6,j=9 

执行for循环体中的最后一句,i=6,j=10

 执行for()中的第三个语句,i=7,j=9 

执行判断条件,i=7,j=9 

执行for循环体中的最后一句,i=7,j=10

 执行for()中的第三个语句,i=8,j=9 

执行判断条件,i=8,j=9 

执行for循环体中的最后一句,i=8,j=10

 执行for()中的第三个语句,i=9,j=9 

执行判断条件,i=9,j=9 

 9,9 








下面讲述for循环在遍历过程中的使用


J2SE 1.5提供了另一种形式的for循环。借助这种形式的for循环,可以用更简单地方式来遍历数组和Collection等类型的对象。本文介绍使用这种循环的具体方式,说明如何自行定义能被这样遍历的类,并解释和这一机制的一些常见问题。

在Java程序中,要“逐一处理”――或者说,“遍历”――某一个数组或Collection中的元素的时候,一般会使用一个for循环来实现(当然,用其它种类的循环也不是不可以,只是不知道是因为for这个词的长度比较短,还是因为for这个词的含义和这种操作比较配,在这种时候for循环比其它循环常用得多)。

对于遍历数组,这个循环一般是采取这样的写法:

清单1:遍历数组的传统方式

/* 建立一个数组 */

int[] integers = {1, 2, 3, 4};

/* 开始遍历 */

for (int j = 0; j<integers.length; j++){

int i = integers[j];

System.out.println(i);

}

而对于遍历Collection对象,这个循环则通常是采用这样的形式

清单2:遍历Collection对象的传统方式

/* 建立一个Collection */

String[] strings = {"A", "B", "C", "D"};

Collection stringList = java.util.Arrays.asList(strings);

/* 开始遍历 */

for (Iterator itr = stringList.iterator(); itr.hasNext();) {

Object str = itr.next();

System.out.println(str);

}

而在Java语言的最新版本――J2SE 1.5中,引入了另一种形式的for循环。借助这种形式的for循环,现在可以用一种更简单地方式来进行遍历的工作。

1. 第二种for循环

不严格的说,Java的第二种for循环基本是这样的格式:

for (循环变量类型 循环变量名称 : 要被遍历的对象) 循环体

借助这种语法,遍历一个数组的操作就可以采取这样的写法:

清单3:遍历数组的简单方式

/* 建立一个数组 */

int[] integers = {1, 2, 3, 4};

/* 开始遍历 */

for (int i : integers) {

System.out.println(i);/* 依次输出“1”、“2”、“3”、“4” */

}

这里所用的for循环,会在编译期间被看成是这样的形式:

清单4:遍历数组的简单方式的等价代码

/* 建立一个数组 */

int[] integers = {1, 2, 3, 4};

/* 开始遍历 */

for (int 变量名甲 = 0; 变量名甲<integers.length; 变量名甲++)="" {

System.out.println(integers[变量名甲]);/* 依次输出“1”、“2”、“3”、“4” */

}

这里的“变量名甲”是一个由编译器自动生成的不会造成混乱的名字。

而遍历一个Collection的操作也就可以采用这样的写法:

清单5:遍历Collection的简单方式

/* 建立一个Collection */

String[] strings = {"A", "B", "C", "D"};

Collection list = java.util.Arrays.asList(strings);

/* 开始遍历 */

for (Object str : list) {

System.out.println(str);/* 依次输出“A”、“B”、“C”、“D” */

}

这里所用的for循环,则会在编译期间被看成是这样的形式:

清单6:遍历Collection的简单方式的等价代码

/* 建立一个Collection */

String[] strings = {"A", "B", "C", "D"};

Collection stringList = java.util.Arrays.asList(strings);

/* 开始遍历 */

for (Iterator 变量名乙 = list.iterator(); 变量名乙.hasNext();) {

Object str = 变量名乙.next();

System.out.println(str);/* 依次输出“A”、“B”、“C”、“D” */

}

这里的“变量名乙”也是一个由编译器自动生成的不会造成混乱的名字。

因为在编译期间,J2SE 1.5的编译器会把这种形式的for循环,看成是对应的传统形式,所以不必担心出现性能方面的问题。

不用“foreach”和“in”的原因

Java采用“for”(而不是意义更明确的“foreach”)来引导这种一般被叫做“for-each循环”的循环,并使用“:”(而不是意义更明确的“in”)来分割循环变量名称和要被遍历的对象。这样作的主要原因,是为了避免因为引入新的关键字,造成兼容性方面的问题――在Java语言中,不允许把关键字当作变量名来使用,虽然使用“foreach”这名字的情况并不是非常多,但是“in”却是一个经常用来表示输入流的名字(例如java.lang.System类里,就有一个名字叫做“in”的static属性,表示“标准输入流”)。

的确可以通过巧妙的设计语法,让关键字只在特定的上下文中有特殊的含义,来允许它们也作为普通的标识符来使用。不过这种会使语法变复杂的策略,并没有得到广泛的采用。

“for-each循环”的悠久历史

“for-each循环”并不是一个最近才出现的控制结构。在1979正式发布的Bourne shell(第一个成熟的UNIX命令解释器)里就已经包含了这种控制结构(循环用“for”和“in”来引导,循环体则用“do”和“done”来标识)。

2. 防止在循环体里修改循环变量

在默认情况下,编译器是允许在第二种for循环的循环体里,对循环变量重新赋值的。不过,因为这种做法对循环体外面的情况丝毫没有影响,又容易造成理解代码时的困难,所以一般并不推荐使用。

Java提供了一种机制,可以在编译期间就把这样的操作封杀。具体的方法,是在循环变量类型前面加上一个“final”修饰符。这样一来,在循环体里对循环变量进行赋值,就会导致一个编译错误。借助这一机制,就可以有效的杜绝有意或无意的进行“在循环体里修改循环变量”的操作了。

清单7:禁止重新赋值

int[] integers = {1, 2, 3, 4};

for (final int i : integers) {

i = i / 2; /* 编译时出错 */

}

注意,这只是禁止了对循环变量进行重新赋值。给循环变量的属性赋值,或者调用能让循环变量的内容变化的方法,是不被禁止的。

清单8:允许修改状态

Random[] randoms = new Random[]{new Random(1), new Random(2), new Random(3)};

for (final Random r : randoms) {

r.setSeed(4);/* 将所有Random对象设成使用相同的种子 */

System.out.println(r.nextLong());/* 种子相同,第一个结果也相同 */

}

3. 类型相容问题

为了保证循环变量能在每次循环开始的时候,都被安全的赋值,J2SE 1.5对循环变量的类型有一定的限制。这些限制之下,循环变量的类型可以有这样一些选择:

循环变量的类型可以和要被遍历的对象中的元素的类型相同。例如,用int型的循环变量来遍历一个int[]型的数组,用Object型的循环变量来遍历一个Collection等

清单9:使用和要被遍历的数组中的元素相同类型的循环变量

int[] integers = {1, 2, 3, 4};

for (int i : integers) {

System.out.println(i);/* 依次输出“1”、“2”、“3”、“4” */

}

清单10:使用和要被遍历的Collection中的元素相同类型的循环变量

Collection< String> strings = new ArrayList< String>();

strings.add("A");

strings.add("B");

strings.add("C");

strings.add("D");

for (String str : integers) {

System.out.println(str);/* 依次输出“A”、“B”、“C”、“D” */

}

循环变量的类型可以是要被遍历的对象中的元素的上级类型。例如,用int型的循环变量来遍历一个byte[]型的数组,用Object型的循环变量来遍历一个Collection< String>(全部元素都是String的Collection)等。

清单11:使用要被遍历的对象中的元素的上级类型的循环变量

String[] strings = {"A", "B", "C", "D"};

Collection< String> list = java.util.Arrays.asList(strings);

for (Object str : list) {

System.out.println(str);/* 依次输出“A”、“B”、“C”、“D” */

}

循环变量的类型可以和要被遍历的对象中的元素的类型之间存在能自动转换的关系。J2SE 1.5中包含了“Autoboxing/Auto-Unboxing”的机制,允许编译器在必要的时候,自动在基本类型和它们的包裹类(Wrapper Classes)之间进行转换。因此,用Integer型的循环变量来遍历一个int[]型的数组,或者用byte型的循环变量来遍历一个Collection< Byte>,也是可行的。

清单12:使用能和要被遍历的对象中的元素的类型自动转换的类型的循环变量

int[] integers = {1, 2, 3, 4};

for (Integer i : integers) {

System.out.println(i);/* 依次输出“1”、“2”、“3”、“4” */

}

注意,这里说的“元素的类型”,是由要被遍历的对象的决定的――如果它是一个Object[]型的数组,那么元素的类型就是Object,即使里面装的都是String对象也是如此。

可以限定元素类型的Collection

截至到J2SE 1.4为止,始终无法在Java程序里限定Collection中所能保存的对象的类型――它们全部被看成是最一般的Object对象。一直到J2SE 1.5中,引入了“泛型(Generics)”机制之后,这个问题才得到了解决。现在可以用Collection< T>来表示全部元素类型都是T的Collection。

<think>好的,我需要帮助用户分析给定的Java for-each循环遍历Map<String, Object>的写法。首先,用户提供的代码片段中有几个引用,我需要先看一下这些引用中的例子是否符合用户的问题。 引用[1]中的例子使用了parallelStream来遍历Map,而用户问的是for-each循环,这可能不是直接相关,但可以对比说明不同遍历方式。引用[2][3]中的代码涉及到了Map的创建嵌套结构,但遍历方式可能不是传统的for-each。引用[4]提到了Thymeleaf遍历List,可能Java本身的循环关系不大,但可以忽略这部分。 接下来,我需要回忆Java中遍历Map的几种常见方法。用户特别提到for-each循环,所以应该重点讲解entrySet()结合增强for循环的方式。例如,通过Map.entrySet()获取键值对集合,然后使用for-each逐个处理每个entry,用entry.getKey()entry.getValue()来获取键值。 另外,用户给出的示例代码中可能没有直接的for-each例子,但引用[1]中的parallelStream虽然不同,但可以对比说明。需要指出传统的for-eachStream API的区别,比如是否并行处理,是否需要顺序等。 还要考虑Map的其他遍历方式,比如遍历keySet()或values(),但用户的问题明确是关于遍历Map<String, Object>,所以应该以entrySet为重点,因为这样能同时访问键值,比较常用。 需要提醒用户注意类型安全,尤其是在使用泛型时,确保Map的声明正确,避免出现类型转换错误。例如,如果Map的值是Object类型,取出时可能需要根据实际类型进行强制转换。 另外,代码中如果存在嵌套的Map结构,比如引用[2]中的scourse字段是一个嵌套的Map,遍历时可能需要递归处理或类型检查,比如使用instanceof判断是否为Map,再进一步遍历。 最后,总结一下正确的写法步骤,并给出代码示例,说明每个部分的作用。可能还需要提到遍历时的顺序问题,比如HashMap不保证顺序,而LinkedHashMap会保持插入顺序,TreeMap会排序等,这些可能影响遍历结果。 可能还需要比较不同遍历方式的性能,比如entrySet的效率通常比先遍历keySet再get(key)更高,因为后者需要多次查找。但用户的问题主要是写法,所以这点可以作为优化建议提及。 总结思路后,组织回答的结构:首先介绍标准的for-each写法,然后解析各部分,接着给出示例代码,并分析注意事项,如类型转换、嵌套结构处理,最后可能对比其他遍历方法,如Iterator或Stream API,并引用相关例子中的内容作为扩展。</think>### Java for-each循环遍历$Map<String, Object>$的写法及解析 #### 1. **标准遍历写法** Java中遍历$Map<String, Object>$的常用方式是使用**增强for循环for-each)**结合$entrySet()$方法,直接获取键值对。具体步骤如下: - **获取键值对集合**:通过$map.entrySet()$获取所有键值对的集合,类型为$Set<Map.Entry<String, Object>>$。 - **遍历键值对**:通过增强for循环逐个处理每个$Entry$对象,使用$getKey()$$getValue()$方法获取键值。 **示例代码**: ```java Map<String, Object> map = new HashMap<>(); map.put("name", "张三"); map.put("age", 20); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); System.out.println(key + ": " + value); } ``` #### 2. **代码解析** - **$entrySet()$的作用**:将Map转换为包含键值对的集合,每个元素是$Map.Entry$类型[^1]。 - **类型声明**:$Map.Entry<String, Object>$需指定泛型类型,与Map声明一致。 - **值类型处理**:若值可能包含嵌套结构(如另一个Map或List),需通过类型检查强制转换处理。例如: ```java if (value instanceof Map) { Map<String, String> nestedMap = (Map<String, String>) value; // 遍历嵌套Map } ``` #### 3. **对比其他遍历方式** | **方式** | **特点** | **适用场景** | |-----------------------|------------------------------------------------------------------------|--------------------------| | **keySet()遍历** | 先遍历所有键,再通过$map.get(key)$获取值,效率较低(需多次哈希查找)[^1] | 仅需处理键时使用 | | **values()遍历** | 直接遍历所有值,但无法获取键 | 仅需处理值时使用 | | **Iterator遍历** | 通过迭代器操作,支持遍历时删除元素 | 需要边遍历边修改集合时使用 | | **Stream API遍历** | 支持并行处理(如引用[1]中的$parallelStream()$),适合大数据量函数式编程需求[^1] | 需要链式操作或并行处理的场景 | #### 4. **嵌套结构处理示例** 若Map的值包含嵌套的$Map<String, Object>$(如引用[2]中的学生课程信息),需递归遍历: ```java for (Map.Entry<String, Object> entry : map.entrySet()) { Object value = entry.getValue(); if (value instanceof Map) { for (Map.Entry<?, ?> nestedEntry : ((Map<?, ?>) value).entrySet()) { System.out.println("嵌套键值对: " + nestedEntry.getKey() + "=" + nestedEntry.getValue()); } } } ``` #### 5. **注意事项** - **类型安全**:从Map中取出Object类型的值时,需明确实际类型并强制转换,避免$ClassCastException$。 - **空值检查**:若Map可能包含$null$值,需用$if (value != null)$判断。 - **遍历顺序**:HashMap不保证顺序,需有序遍历时应使用$LinkedHashMap$或$TreeMap$。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值