“Foreach(增强for)”的版本间差异
		
		
		
		
		
		跳到导航
		跳到搜索
		
				
		
		
	
|  (→使用) | |||
| 第15行: | 第15行: | ||
| === 使用 === | === 使用 === | ||
| <syntaxhighlight lang="java"> | # 遍历数组: | ||
| #: <syntaxhighlight lang="java"> | |||
| public class ForeachTest | public class ForeachTest | ||
| { | { | ||
| 第21行: | 第22行: | ||
|      { |      { | ||
|          int[] arr = {1, 2, 3, 4, 5}; |          int[] arr = {1, 2, 3, 4, 5}; | ||
|         int[][] arr2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; | |||
|          System.out.println("---------- |          System.out.println("---------- for ------------"); | ||
|          for(int i=0; i<arr.length; i++) |          for(int i=0; i<arr.length; i++) | ||
|          { |          { | ||
| 第28行: | 第30行: | ||
|          } |          } | ||
|          System.out.println("--------- |          System.out.println("--------- foreach -------------"); | ||
|          for(int element:arr) |          for(int element:arr) | ||
|          { |          { | ||
| 第34行: | 第36行: | ||
|          } |          } | ||
|          System.out.println("--------- |          System.out.println("--------- foreach 遍历二维数组 -------------"); | ||
|          for(int[] row : arr2) |          for(int[] row : arr2) | ||
|          { |          { | ||
| 第42行: | 第43行: | ||
|                  System.out.println(element); |                  System.out.println(element); | ||
|              } |              } | ||
|         } | |||
|     } | |||
| } | |||
| </syntaxhighlight> | |||
| # 遍历集合(三种方式): | |||
| #: <syntaxhighlight lang="java"> | |||
| public class ForeachTest | |||
| { | |||
|     public static void main(String[] args) | |||
|     {  | |||
|         List<String> list = new ArrayList<String>(); | |||
|         list.add("a"); | |||
|         list.add("b"); | |||
|         list.add("c"); | |||
|         System.out.println("---------- for -----------"); | |||
|         for(int i = 0; i < list.size(); i++) | |||
|         { | |||
|             System.out.println(list.get(i)); | |||
|         } | |||
|         System.out.println("---------- foreach(实现了Iterable)-----------"); | |||
|         for(String str: list) | |||
|         { | |||
|             System.out.println(str); | |||
|         } | |||
|         System.out.println("---------- Iterator -----------"); | |||
|         for(Iterator<String> iter = list.iterator(); iter.hasNext();) | |||
|         { | |||
|             System.out.println(iter.next()); | |||
|          } |          } | ||
|      } |      } | ||
2022年4月23日 (六) 22:59的版本
关于
For-Each 循环也叫增强型的 for 循环,或者叫 foreach 循环,是 JDK5.0 的新特性(其他新特性比如泛型、自动装箱等),For-Each 循环的加入简化了集合的遍历。
这种方式并不是新的语法,只是语法糖。即编写的 foreach 循环的代码并不是直接转成字节码,而是由编译器先转成对应的语法,然后再转成字节码,可以理解成是编译器对一些语法的封装提供的另一种方便阅读编写功能代码的实现方式。
语法
for(type element: array)
{
   System.out.println(element);
}
使用
- 遍历数组:
- public class ForeachTest { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; int[][] arr2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; System.out.println("---------- for ------------"); for(int i=0; i<arr.length; i++) { System.out.println(arr[i]); } System.out.println("--------- foreach -------------"); for(int element:arr) { System.out.println(element); } System.out.println("--------- foreach 遍历二维数组 -------------"); for(int[] row : arr2) { for(int element : row) { System.out.println(element); } } } } 
 
- 遍历集合(三种方式):
- public class ForeachTest { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); System.out.println("---------- for -----------"); for(int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } System.out.println("---------- foreach(实现了Iterable)-----------"); for(String str: list) { System.out.println(str); } System.out.println("---------- Iterator -----------"); for(Iterator<String> iter = list.iterator(); iter.hasNext();) { System.out.println(iter.next()); } } } 
 
原理
foreach 可以与任何实现了 Iterable 接口的对象一起工作。
(Iterable 接口提供了一个 Iterator 迭代器)
Java 中提供的 foreach 语法糖其底层实现方式主要有两种:
- 迭代器遍历模式:对于集合类(或实现迭代器的集合),使用“迭代器”的遍历方式;
- 以下两组代码等效:
- import java.util.ArrayList; import java.util.List; public class TestForEach { public static void main(String args[]) { List<Integer> nums = new ArrayList<>(); nums.add(11); nums.add(22); nums.add(33); for (Integer num : nums) { System.out.println(num); } } } 
- import java.util.ArrayList; import java.util.List; import java.util.Iterator; public class TestForEach { public static void main(String args[]) { List<Integer> nums = new ArrayList<>(); nums.add(11); nums.add(22); nums.add(33); // 此处没有使用泛型,因为泛型在 java 中也是一种语法糖,只是编译器提供的一种检查,在运行期会擦除类型信息,其并不像C++那样在语法层面真正的支持泛型 // 当然,为了良好的编码习惯,在平时的编码中应该使用泛型,即 Iterator<Integer> iter = nums.iterator(); Iterator iter = nums.iterator(); while (iter.hasNext()) { Integer num = (Integer)iter.next(); System.out.println(num); } } } 
 
- 数组依次遍历模式:对于数组(没有实现 Iterator 接口)使用最基本的依次遍历数组中每个元素的方式,来实现遍历。
- 以下两组代码等效:
- public class TestForEach { public static void main(String args[]) { int[] nums = {11, 22, 33}; for (int num : nums) { System.out.println(num); } } } 
- public class TestForEach { public static void main(String args[]) { int[] nums = {11, 22, 33}; for (int i = 0; i < nums.length; i++) { int num = nums[i]; System.out.println(num); } } } 
 
性能
由于 foreach 作为语法糖,在不同的场景实现方式不同,所以其性能分别近似于 for(对于数组) 和 Iterator(对于集合)。
由下代码可以看出:
- 对于集合(ArrayList、LinkList),性能表现:Iterator >= foreach > for;
- 不要在 LinkList 中使用 for,效率太低;
 
- 对于数组,性能表现:foreach >= for;
- (数组不支持 Iterator)
 
package com.eijux;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
public class TestFor {
    ArrayList<Integer> arrayList = new ArrayList<>();
    LinkedList<Integer> linkedList = new LinkedList<>();
    String[] strings = new String[100000000];
    public TestFor() {
        for (int i = 0; i < 100000; i++) {
            arrayList.add(i);
            linkedList.add(i);
        }
        for (int i = 0; i < 100000; i++) {
            strings[i] = "eijux" + i;
        }
    }
    public void testArrayList() {
        int number;
        long start, end, result;
        System.out.println("========== ArrayList ==========");
        start = System.currentTimeMillis();
        for (int i = 0; i < arrayList.size(); i++) {
            number = arrayList.get(i);
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("ArrayList for循环消耗时间:" + result);
        start = System.currentTimeMillis();
        for (int i : arrayList) {
            number = i;
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("ArrayList foreach循环消耗时间:" + result);
        start = System.currentTimeMillis();
        Iterator<Integer> itr = arrayList.iterator();
        while (itr.hasNext()) {
            number = itr.next();
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("ArrayList Iterator循环消耗时间:" + result);
    }
    public void testLinkList() {
        int number;
        long start, end, result;
        System.out.println("========== LinkList ==========");
        start = System.currentTimeMillis();
        for (int i = 0; i < linkedList.size(); i++) {
            number = linkedList.get(i);
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("LinkList for循环消耗时间:" + result);
        start = System.currentTimeMillis();
        for (int i : linkedList) {
            number = i;
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("LinkList foreach循环消耗时间:" + result);
        start = System.currentTimeMillis();
        Iterator<Integer> itr = linkedList.iterator();
        while (itr.hasNext()) {
            number = itr.next();
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("LinkList Iterator循环消耗时间:" + result);
    }
    public void testArray() {
        String string;
        long start, end, result;
        System.out.println("========== Array ==========");
        start = System.currentTimeMillis();
        for (int i = 0; i < strings.length; i++) {
            string = strings[i];
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("String[] for循环消耗时间:" + result);
        start = System.currentTimeMillis();
        for (String str : strings) {
            string = str;
        }
        end = System.currentTimeMillis();
        result = end - start;
        System.out.println("String[] foreach循环消耗时间:" + result);
    }
    public static void main(String[] args) {
        (new TestFor()).testArrayList();
        (new TestFor()).testLinkList();
        (new TestFor()).testArray();
    }
}
结果:
- ========== ArrayList ========== ArrayList for循环消耗时间:42 ArrayList foreach循环消耗时间:9 ArrayList Iterator循环消耗时间:7 ========== LinkList ========== LinkList for循环消耗时间:22322 LinkList foreach循环消耗时间:8 LinkList Iterator循环消耗时间:5 ========== Array ========== String[] for循环消耗时间:21 String[] foreach循环消耗时间:11 
为什么不能在 foreach 中增加、删除集合元素?
使用 foreach 进行集合遍历时需要额外注意不能对集合长度进行修改,也就是不能对集合进行增删操作,否则会抛出 ConcurrentModificationException 异常。
因为:
1、在 Iterator 可以利用自身(迭代器的)方法修改集合;
2、在 Iterator 不能使用集合的方法修改集合,否则会导致下一次循环时“集合的 modCount != 迭代器的 expectedModCount”,从而出现上述异常;
但是,在 foreach 中不能调用 Iterator 的方法操作集合,所以“不能在 foreach 中增加、删除集合元素”(可以使用 Iterator 或 for)。
- 错误方式:
- import java.util.ArrayList; import java.util.List; public class TestForEach { public static void main(String args[]) { List<Integer> nums = new ArrayList<>(); nums.add(11); nums.add(22); nums.add(33); for (Integer num : nums) { if (num == 11) { // 此处使用集合中的 remove 操作,而不是迭代器中的 remove 操作, // 会导致迭代器中的 expectedModCount 和集合中的 modCount 变量不相等,从而导致在执行 next() 函数时抛出异常 nums.remove((Integer)num); } else { System.out.println(num); } } } } 
 
- 正确方式:【使用 Iterator】
- import java.util.ArrayList; import java.util.List; import java.util.Iterator; public class TestForEach { public static void main(String args[]) { List<Integer> nums = new ArrayList<>(); nums.add(11); nums.add(22); nums.add(33); Iterator<Integer> iter = nums.iterator(); while (iter.hasNext()) { Integer num = iter.next(); if (num == 11) { // 在迭代器遍历中,不能使用集合自有的删除操作,只能使用迭代器中的删除操作, // 否则会导致迭代器中的 expectedModCount 和集合中的 modCount 变量不相等,从而导致在执行 next() 函数时抛出异常 //nums.remove((Integer)num); iter.remove(); } else { System.out.println(num); } } } } 
 
集合的“modCount” 与 迭代器的“expectedModCount”
Iterator 的每次迭代(以及每个方法)执行前,都会判断“modCount”与“expectedModCount”是否相等,然后才进行下一步操作,否则抛出“ConcurrentModificationException”异常。
域含义:
- modCount:用于记录“修改集合”的次数。
- 定义于抽象类“AbstractList<E>”(ArrayList、LinkedList 等直接或间接继承于该抽象类);
- “修改集合”包括:改变集合大小(“增加”、“删除”);改变集合内容(“修改”:“clear”、“replace”、“sort”,但不包括“set”);
- 比如:
 - ArrayList :add、remove、trimToSize、ensureCapacity,与 clear、replace、sort 等,以及相关方法。【但不包括“set”的相关方法】
- LinkedList:link、unlink、add、remove、poll、offer、push、pop,与 clear 等,以及相关方法。【但不包括“set”的相关方法】
 
 
- expectedModCount:用于记录 Iterator 初始化时,集合的 modCount 值。
- 定义于集合类的迭代器(内部类“private class ListItr implements ListIterator<E>”);
 
修改集合时:
- 通过“集合方法”修改:modCount 增加,expectedModCount 不变;
- 通过“Iterator 方法”修改:modCount 增加,expectedModCount 增加(或与 modCount 同步);
判断 modCount 与 expectedModCount 相等的意义? fail-fast策略 java.util.ArrayList 不是线程安全的,在迭代过程中,通过判断 modCount 跟 expectedModCount 是否相等,就可以获知在使用迭代器的过程中是否有其他线程修改了 list。 所以,当需要遍历那些非线程安全的数据结构时,尽量使用迭代器。(或 foreach)
ArrayList 内部迭代器源码
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;
        Itr() {}
        public boolean hasNext() {
            return cursor != size;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
关于“foreach 不能修改集合元素本身,但是可以更改集合元素的属性”
网上有如上说法:“如果是对象集合,不能修改集合元素本身,但是可以更改集合元素的属性”
比如:
    有集合 ArrayList<User> users = new ArrayList<>();
    
    // 非法:
    for (User user : users) {
         user = new User("Eijux", 26, "成都");
         ...
    }
    
    // 合法:
    for (User user : users) {
         user.setAddress("成都");
         ...
    }
这一说法并不准确(虽然效果来看确实如此)。
这一现象其实是由于“Java 的值传递”[1]导致:
此处:
- 实参(为:集合元素),形参(为:user);
- 因为:“形参”与“实参”的属性,指向内存中的同一地址;
- 所以,形参方法“user.setAddress()”,也能修改实参(集合元素)的属性。
简而言之:
在 foreach 中并没有修改实参(集合元素),而是修改了形参(user)。