目录

Java迭代器详解,看这一篇就够了

Java迭代器详解,看这一篇就够了

文章目录

🚩Java 迭代器详解

📚迭代器的定义

迭代器 是属于 设计模式 之一, 迭代器模式 提供了一种方法来顺序访问一个聚合对象中各个元素,而不保留该对象的内部表示。

1) Iterator对象 称为 迭代器 ,主要用于遍历 Collection集合 中的元素。

2)所有实现了 Collection接口 的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator接口的对象 ,即可以返回一个 迭代器

3) Iterator 仅用于遍历集合, Iterator 本身并不存放对象。

📒认识Iterator

✏️类结构图

https://i-blog.csdnimg.cn/blog_migrate/5160b869b02d7f435016d90aca9958a2.png

通过观察类结构图的继承关系我们发现,集合的顶层接口 Collection 继承 Iterable 接口。

✒️Iterable接口

public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();
}

Iterable接口 中有一个 Iterator方法 ,它返回一个 Itertator对象

🖍️Iterator接口

public interface Iterator<E> {
    boolean hasNext();
    
    E next();
    
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}
📃Iterator接口的方法
返回值类型方法名功能
booleanhasNext()判断集合是否还有元素,如果返回 true 表示集合还有元素,返回 false 表示集合中没有元素;一般对集合的访问通过 while(hasNext()) 判断是否还需要遍历。
Enext()获取集合中遍历的当前元素 ;一般先调用 hasNext() 方法判断是否存在元素,再调用 next() 获取元素,需要进行循环交替遍历集合中的元素。
voidremove删除集合中的元素。

📙迭代器的使用

🏷️使用迭代器遍历集合

我们用 ArrayList集合 存放一些整型数据做示例,然后将其集合中的元素一一遍历打印输出。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);

        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()) {
            int value = iterator.next();
            System.out.print(value + " ");
        }
    }
}

运行结果:

https://i-blog.csdnimg.cn/blog_migrate/3e7c4a681b2d3ac79efb5f2d91c638c5.png

观察运行结果我们发现,通过 迭代器 我们将 ArrayList集合 中的元素一一打印了出来。

🔖Itertor的执行原理

在上述的示例中, 迭代器 是如何实现对集合的遍历呢?

⏳图示执行过程

https://i-blog.csdnimg.cn/blog_migrate/25c06405128bdd89361a8995762dc65f.gif

⌛执行过程详解

①首先得到一个集合的迭代器 Iterator iterator = list.iterator();

②进入 while循环 ,调用 hasNext() 判断是否有下一个元素,返回true, Iterator.next() 移动一个位置,将该位置的元素 111 返回。

③再次进入 while循环 ,调用 hasNext() 判断是否有下一个元素,返回true, Iterator.next() 移动一个位置,将该位置 222 的元素返回。

④再次进入 while循环 ,调用 hasNext() 判断是否有下一个元素,返回true, Iterator.next() 移动一个位置,将该位置 333 的元素返回。

⑤再次进入 while循环 ,调用 hasNext() 判断是否有下一个元素,返回false,循环结束。


在 迭代器 的遍历过程中先通过 hastNext() 方法判断是否有下一个元素,如果存在下一个元素再调用 next() 方法获取元素,在这里 next() 方法先往后移动一个元素位置,再返回该位置的元素。因此,在调用 next() 方法之前必须要调用 hastNext() 方法进行检测;如果没有调用并且没有下一个元素,直接调用 next() 方法会抛出 NoSuchElementException异常

🃏生成迭代器的快捷键

一开始使用 迭代器 可能会觉得麻烦,但是如果用的 Idea编译器 是有快捷键的,输入 itit 再回车就会直接生成。

https://i-blog.csdnimg.cn/blog_migrate/9ad3a7183b6d8569868983a4e00e412b.gif

📕迭代器中的remove()

⛄迭代器的remove()方法使用

在 Iterator接口 中除了 hasNext()next() 方法外,还有一个 remove() 方法,即 删除集合中的元素

如删除上述示例集合中的元素111

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@SuppressWarnings({"all"})
public class TestDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);

        System.out.println("删除前:" + list);
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer value =  iterator.next();
            if(value == 111) iterator.remove();
        }
        System.out.println("删除后" + list);
    }
}

运行结果:

https://i-blog.csdnimg.cn/blog_migrate/89bf1d9da8a590d47989f4a9e3993fe0.png

☃️迭代器遍历中调用集合revome()方法触发异常

在Java集合中,以集合 ArrayList 为例,在使用中可能会遇到删除的需求场景,此时如果代码书写不当,极有可能会抛出 java.util.ConcurrentModificationException 异常信息。

在上述示例中用 Iterator 调用了迭代器 remove() 方法,如果在使用中不小心调用了集合中的 remove() 方法会发生什么?

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@SuppressWarnings({"all"})
public class TestDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);

        System.out.println("删除前:" + list);
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer value =  iterator.next();
            if(value == 111) list.remove(Integer.valueOf(111));
        }
        System.out.println("删除后" + list);
    }
}

运行结果:

https://i-blog.csdnimg.cn/blog_migrate/836ed7e76786f0621313f3768e5225af.png

运行结果中抛出 java.util.ConcurrentModificationException 异常信息。这是因为触发了 集合中并发修改的异常 接下来我们通过源码对抛出异常的原因进行剖析。

    public Iterator<E> iterator() {
        return new Itr();
    }

ArrayList 集合的 Iterator 方法中,是通过返回 Itr 对象来获得迭代器的。 ItrArrayList 的一个 内部类 ,它实现了 Iterator 接口, 代码如下:

   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();
            }
        }

Itr类属性

属性含义
cursor索引下标,表示下一个可以访问的元素的索引,默认值为 0
lastRet索引下标,表示上一个元素的索引,默认值为 -1
expectedModCount对集合修改的版本号,初始值为 ModCount

ModCount定义在AbstractList接口中,初始值为 0 ,定义如下:

 protected transient int modCount = 0;

ModCount是版本号,在对集合进行变更操作(增加、删除、修改等)的时候会对版本号进行 +1 操作。


结合上述代码进行抛出 java.util.ConcurrentModificationException 异常的解释。

①初始化ArrayList,添加三次元素,即三次调用 add() 方法,进行三次 modCount++; 此时,

m o d C o u n t

3 , s i z e

3 ; \color{red}{modCount = 3,size = 3;}

m

o

d

C

o

u

n

t

=

3

s

i

z

e

=

3

②初始化Iterator迭代器进行循环,此时,

e x p e c t e d M o d C o u n t

m o d C o u n t

3 , \color{red}{expectedModCount = modCount=3,}

e

x

p

e

c

t

e

d

M

o

d

C

o

u

n

t

=

m

o

d

C

o

u

n

t

=

3

c u r s o r

0 , l a s t R e t

− 1 \color{red}{cursor=0,lastRet = -1}

c

u

r

s

o

r

=

0

l

a

s

t

R

e

t

=

1

③进行hasNext判断, cursor != size; 成立,进入循环

④调用 next() 方法,首先进行 checkForComodification() 校验,

m o d C o u n t

= e x p e c t e d M o d C o u n t \color{red}{modCount == expectedModCount}

m

o

d

C

o

u

n

t

=

=

e

x

p

e

c

t

e

d

M

o

d

C

o

u

n

t ,校验通过,返回值,此时

l a s t R e t

0 ; c u r s o r

1 \color{red}{lastRet = 0;cursor = 1}

l

a

s

t

R

e

t

=

0

;

c

u

r

s

o

r

=

1

 final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

⑤调用集合 remove() 方法, modCount++; ,此时

m o d C o u n t

4 ; s i z e

2 \color{red}{modCount = 4;size = 2}

m

o

d

C

o

u

n

t

=

4

;

s

i

z

e

=

2

⑥再次调用hasNext()方法判断, cursor != size; 成立,进入循环

⑦调用 next() 方法进行校验,

m o d C o u n t !

e x p e c t e d M o d C o u n t \color{red}{modCount != expectedModCount}

m

o

d

C

o

u

n

t

!

=

e

x

p

e

c

t

e

d

M

o

d

C

o

u

n

t ,校验未通过,抛出 java.util.ConcurrentModificationException 异常

总结:

①在使用 迭代器 的 remove() 操作时,会将更新后的 modCountexpectedModCount ,两者会得到同步,但是在调用集合的 remove() 方法后,两个不会进行同步,进而导致在 checkForComodification() 校验时不通过,抛出 java.util.ConcurrentModificationException 异常。

②所以,在单线程下使用迭代器是没有问题的,但是在多线程下同时操作集合就不允许了,可以通过 fail-fast 快速失败机制,快速判断是否存在同时操作问题。因此, 集合在多线程下使用是不安全的 。

📗增强for循环

📫认识增强for循环

增强for循环 可以代替 Iterator迭代器 ,可以把它看做简化版的Iterator,和迭代器本质一样,其实它的底层实现就是Iterator迭代器,只能用于 遍历集合或数组 。

📪基本语法

https://i-blog.csdnimg.cn/blog_migrate/80e05a43aa84c98b2ec8f29aa76b5e64.png

📬增强for循环的使用

import java.util.ArrayList;
import java.util.List;

public class TestDemo03 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);

        System.out.println("====增强for循环遍历集合====");
        for(Integer i : list) {
            System.out.print(i + " ");
        }
        System.out.println();

        System.out.println("====增强for循环遍历数组====");
        int[] arr = {1,2,3,4,5,6};
        for (int i : arr) {
            System.out.print(i + " ");
        }

    }
}

运行结果:

https://i-blog.csdnimg.cn/blog_migrate/0415173fee40886edff894660d9cb6f8.png

📭增强for循环的快捷键

与迭代器一样,增强for循环也有快捷键,输入 I 回车即可快速生成。

https://i-blog.csdnimg.cn/blog_migrate/7bdf6867108dd8856e7ae2c1f6c60982.gif