# 迭代器模式
1.简介
迭代器是一种行为设计模式,它可以让你在不暴露集合底层表现形式的情况下遍历集合中的所有元素。
举个例子假如说我们现在有一个 ArrayList
和一个 LinkedList
,如何去遍历集合相信大家都很清楚(fori循环、get(i)输出)。但编写这段代码的前提是我们需要知晓这两个容器的访问方式和内部结构。如果我们现在突然换成比较冷门的比如 HashSet
,那么你该如何遍历,它根本没有提供 get()方法,你这个时候还需要去研究相关的api和内部结构。
但是如果使用迭代器呢?只要它实现了 iterable
接口,无论什么数据结构,也不管你没听过或是没见过,都可以用类似、固定的方式去遍历。
这其实也是上面所说的意思,开发者不需要知道如何去遍历的细节,只需要用类似的遍历方法就好。
2.UML图
- 迭代器(Iterator): 接口声明了遍历集合所需的操作:获取下一个元素、获取当前位置和重新开始迭代等。
- 具体迭代器(Concrete Iterators): 实现遍历集合的一种特定算法。迭代器对象必须跟踪自身遍历的进度。可以使多个迭代器相互独立的遍历同一个集合
- 集合(iterable): 接口声明一个或多个方法来获取与集合兼容的迭代器。返回的方法类型必须被声明为迭代器接口。
- 具体集合(Concrete Iterable): 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。
3、代码示例
Java
中其实已经默认实现了迭代器模式的接口。主要涉及以下两个接口:
package java.util;
import java.util.function.Consumer;
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
package java.lang;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
如果我们希望使自己的类支持迭代器功能,只需要实现使该类实现 Iterable
接口来作为标识。该类只要求实现一个方法,返回一个迭代器。这就要求我们实现 Iterator
(迭代器) 接口。
下面我们写一个类实现迭代器的功能。
首先我们写一个 Class
类,它实现了 Iterable
接口,然后编写一个它的内部类来实现 Iterator
接口。
package com.gs.designmodel.iterator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author: Gaos
* @Date: 2023-08-15 19:26
**/
public class Class implements Iterable<Student>{
private final List<Student> students = new ArrayList<>();
public Class() {
students.add(new Student("学生A", 12));
students.add(new Student("学生B", 20));
students.add(new Student("学生C", 30));
}
public boolean addStudent(Student student) {
return students.add(student);
}
public boolean removeStudent(Student student) {
return students.remove(student);
}
@Override
public Iterator<Student> iterator() {
return new Itr();
}
private class Itr implements Iterator<Student> {
int index = 0;
@Override
public boolean hasNext() {
if(index < students.size()) {
return true;
}
return false;
}
@Override
public Student next() {
Student student = students.get(index);
index++;
return student;
}
}
}
测试类:
package com.gs.designmodel.iterator;
import java.util.Iterator;
/**
* @author: Gaos
* @Date: 2023-08-15 19:47
**/
public class Test {
public static void main(String[] args) {
Class cls = new Class();
Iterator<Student> iterator = cls.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 当然我们也可以使用foreach 它已经默认实现了
for (Student item : cls) {
System.out.println(item);
}
}
}
输出:
Student(name=学生A, year=12)
Student(name=学生B, year=20)
Student(name=学生C, year=30)
Student(name=学生A, year=12)
Student(name=学生B, year=20)
Student(name=学生C, year=30)
那么大家写到这里可能有有些疑问,为什么不直接实现 Iterator
接口,而是要实现 Iterable
接口,再写一个内部类来实现 Iterator
呢?
首先因为 Iterator
接口的核心方法 next()
, hasNext()
都是依赖于迭代器的当前迭代位置的,所以这也是我们上面写的内部类中会有一个 index
变量来进行记录的原因。如果由我们的 Class
类直接去实现 Iterator
,那就会导致 Class
中也需要储存这个变量。
那么问题就出现了,如果我们一个 Class
类实例被多个地方迭代, 对于一个实例来说这个 index
变量是共享的,一个线程A假如把这个变量设置为第三个位置,那么对于线程B来说就无法正确迭代了。实现 Iterator
接口,提供一个方法返回一个实例,其实相当于做了一个隔离,每次调用都返回一个新的实例。这样对于多个迭代器来说达到了互不干扰的效果,这在我们的使用场景中是必须保障的。
4.总结
当集合背后为复杂的数据结构,而且你希望对客户端隐藏其复杂性的时候,你可以使用迭代器模式。使用该模式也可以减少程序中重复的遍历代码。
跟一开始举的例子一样,用户使用起来无需关心细节,简单很多。但是对于简单的遍历,使用迭代器比较繁琐而且效率不高。