详解JAVA中的for-each循环与迭代

 更新时间:2016年10月24日 14:22:40   作者:知其然,后知其所以然  
本文详解了JAVA中的for-each循环与迭代,是JS入门学习中的基础知识,也是java中的难点知识,需要的朋友可以参考下 。

在学习java中的collection时注意到,collection层次的根接口Collection实现了Iterable<T>接口(位于java.lang包中),实现这个接口允许对象成为 "foreach" 语句的目标,而此接口中的唯一方法,实现的就是返回一个在一组 T 类型的元素上进行迭代的迭代器。

一、迭代器Iterator

接口:Iterator<T>

public interface Iterator<E>{
  boolean hasNext(); 
 E next(); 
 void remove();
 }

查看Iterator接口API可以知道,这是对collection进行迭代的迭代器。迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。

尤其值得注意的是此迭代器remove()方法的使用:从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。每次调用 next 只能调用一次此方法。如果进行迭代时用调用此方法(remove方法)之外的其他方式修改了该迭代器所指向的 collection,则迭代器的行为是不确定的。 接口设计人员在设计Iterator<T>接口的时候已经指出,在进行迭代时如果调用了除了迭代器的remove()方法修改了该迭代器所指向的collection,则会造成不确定的后果。具体出现什么后果依迭代器的具体实现而定。针对这种不确定的后果可能出现的情况,在学习ArrayList时遇到了其中一种:迭代器抛出 ConcurrentModificationException异常。具体异常情况如下代码所示:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class ItaratorTest {

  public static void main(String[] args) {
    Collection<String> list = new ArrayList<String>();
    list.add("Android");
    list.add("IOS");
    list.add("Windows Mobile");

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
      String lang = iterator.next();
      list.remove(lang);//will throw ConcurrentModificationException
    }
  }

}

此段代码在运行时会抛出ConcurrentModificationException异常,因为我们在迭代器运行期间没有用iterator的remove()方法来删除元素,而是使用ArrayList的 remove()方法改变了迭代器所指向的collection。这就违反了迭代器的设计原则,所以发生了异常。

所报异常情况如下所示:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at Text.ItaratorTest.main(ItaratorTest.java:17)

二、for-each循环与迭代器Iterator<T>

从Java5起,在Java中有了for-each循环,可以用来循环遍历collection和array。Foreach循环允许你在无需保持传统for循环中的索引,或在使用iterator /ListIterator(ArrayList中的一种迭代器实现)时无需调用while循环中的hasNext()方法就能遍历collection。for-each循环简化了任何Collection或array的遍历过程。但是使用foreach循环也有两点需要注意。

使用foreach循环的对象,必须实现了Iterable<T>接口

请看如下示例:

import java.util.ArrayList;

public class ForeachTest1 {

  public static void main(String args[]) {
    CustomCollection<String> myCollection = new CustomCollection<String>();
    myCollection.add("Java");
    myCollection.add("Scala");
    myCollection.add("Groovy");

    // What does this code will do, print language, throw exception or
    // compile time error
    for (String language : myCollection) {
      System.out.println(language);
    }
  }

  private class CustomCollection<T> {
    private ArrayList<T> bucket;

    public CustomCollection() {
      bucket = new ArrayList();
    }

    public int size() {
      return bucket.size();
    }

    public boolean isEmpty() {
      return bucket.isEmpty();
    }

    public boolean contains(T o) {
      return bucket.contains(o);
    }

    public boolean add(T e) {
      return bucket.add(e);
    }

    public boolean remove(T o) {
      return bucket.remove(o);
    }

  }
}

上述代码将无法通过编译,这是因为代码中的CustomCollection类没有实现Iterable<T>接口,编译期的报错如下:

Exception in thread "main" java.lang.Error: Unresolved compilation problem:
    Can only iterate over an array or an instance of java.lang.Iterable

    at Text.ForeachTest1.main(ForeachTest1.java:15)

事实上,无需等到编译时才发现报错,eclipse会在这段代码写完之后就会在foreach循环处显示错误:Can only iterate over an array or an instance of java.lang.Iterable

从上述示例可以再次得到确认的是,foreach循环只适用于实现了Iterable<T>接口的对象。由于所有内置Collection类都实现了java.util.Collection接口,已经继承了Iterable,所以为了解决上述问题,可以选择简单地让CustomCollection实现Collection接口或者继承AbstractCollection。解决方式如下:

import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Iterator;

public class ForeachTest {
  public static void main(String args[]) {
    CustomCollection<String> myCollection = new CustomCollection<String>();
    myCollection.add("Java");
    myCollection.add("Scala");
    myCollection.add("Groovy");
    for (String language : myCollection) {
      System.out.println(language);
    }
  }

  private static class CustomCollection<T> extends AbstractCollection<T> {
    private ArrayList<T> bucket;

    public CustomCollection() {
      bucket = new ArrayList();
    }

    public int size() {
      return bucket.size();
    }

    public boolean isEmpty() {
      return bucket.isEmpty();
    }

    public boolean contains(Object o) {
      return bucket.contains(o);
    }

    public boolean add(T e) {
      return bucket.add(e);
    }

    public boolean remove(Object o) {
      return bucket.remove(o);
    }

    @Override
    public Iterator<T> iterator() {
      // TODO Auto-generated method stub
      return bucket.iterator();
    }
  }
}

2.foreach循环的内部实现也是依靠Iterator进行实现的

为了验证foreach循环是使用Iterator作为内部实现这一事实,我们依然采用本文最开始的实例进行验证:

public class ItaratorTest {

  public static void main(String[] args) {
    Collection<String> list = new ArrayList<String>();
    list.add("Android");
    list.add("IOS");
    list.add("Windows Mobile");

    // example1
    // Iterator<String> iterator = list.iterator();
    // while (iterator.hasNext()) {
    // String lang = iterator.next();
    // list.remove(lang);
    // }

    // example 2
    for (String language : list) {
      list.remove(language);
    }
  }

}

程序运行时所报异常:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at Text.ItaratorTest.main(ItaratorTest.java:22)

此异常正说明了for-each循环内部使用了Iterator来遍历Collection,它也调用了Iterator.next(),这会检查(元素的)变化并抛出ConcurrentModificationException。

总结:

  • 在遍历collection时,如果要在遍历期间修改collection,则必须通过Iterator/listIterator来实现,否则可能会发生“不确定的后果”。
  • foreach循环通过iterator实现,使用foreach循环的对象必须实现Iterable接口

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java中一维二维数组的静态和动态初始化

    Java中一维二维数组的静态和动态初始化

    今天通过本文给大家分享Java中的数组,包括一维数组和二维数组的静态初始化和动态初始化问题,感兴趣的朋友一起看看吧
    2017-10-10
  • java如何保证多个线程按一定顺序执行

    java如何保证多个线程按一定顺序执行

    这篇文章主要介绍了java如何保证多个线程按一定顺序执行问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Maven本地打包war包实现代码解析

    Maven本地打包war包实现代码解析

    这篇文章主要介绍了Maven本地打包war包实现代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Java并发编程之volatile与JMM多线程内存模型

    Java并发编程之volatile与JMM多线程内存模型

    这篇文章主要介绍了Java并发volatile与JMM多线程内存模型,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • 解决使用@Value(${×××))从properties文件取值的坑

    解决使用@Value(${×××))从properties文件取值的坑

    这篇文章主要介绍了解决使用@Value(${×××))从properties文件取值的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • java为什么需要虚拟机jvm原理详解

    java为什么需要虚拟机jvm原理详解

    这篇文章主要为大家介绍了java为什么需要虚拟机jvm的原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2021-11-11
  • Java打印星号图案和数字图案的示例代码

    Java打印星号图案和数字图案的示例代码

    在 Java 中打印图案是一项常见的编程任务,尤其在初学阶段,通过以特定方式排列符号或数字,可以形成各种设计或形状,这些图案不仅有助于解决问题,还能培养算法思维能力,本文将讨论如何在 Java 中打印图案,并探索一些最常见的图案类型,需要的朋友可以参考下
    2024-11-11
  • maven项目在实践中的构建管理之路的方法

    maven项目在实践中的构建管理之路的方法

    这篇文章主要介绍了maven项目在实践中的构建管理之路的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-05-05
  • SpringBoot集成Hadoop对HDFS的文件操作方法

    SpringBoot集成Hadoop对HDFS的文件操作方法

    这篇文章主要介绍了SpringBoot集成Hadoop对HDFS的文件操作方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-07-07
  • java中跨域问题解决的几种方式

    java中跨域问题解决的几种方式

    这篇文章主要给大家介绍了关于java中跨域问题解决的几种方式, 在前后端分离项目中,经常会遇到跨域问题,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07

最新评论