用了java这么多年你有实现过自己的迭代器(iterator)吗

312 阅读5分钟

大家好,这里是小奏,觉得文章不错可以关注公众号小奏技术

Iterable接口的作用

Iterable接口主要是通过iterator()返回一个Iterator对象,使对象可以在foreach中使用

实现自己自定义的迭代行为

不用迭代器的方法遍历

一般我们传统开发中,不会直接自己实现Iterable接口,都是使用实现了Iterable接口的集合进行遍历。

比我们我们有一个老师对象,老师下面有很多学生,那么一般的做法就是

  1. 首先定义一个学生对象
public class Student {
    
    String name;
    
    String age;
    
}
  1. 然后定义一个老师对象,然老师去持有学生
public class Teacher {

    private List<Student> students;

}

如果我们想要获取所有学生对象然后进行一些业务逻辑操作就是如下方式

public class Main {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        
        List<Student> students = teacher.getStudents();
        
        for (Student student : students) {
            System.out.println(student.name);
        }
    }
}

可以看到我们并不能直接操作Teacher对象,而是通过Teacher对象获取到List<Student>对象,然后通List对象进行遍历。因为List接口继承了Iterable接口,所以我们可以直接使用foreach进行遍历

如果上面的Teacher对象实现了Iterable接口,那么我们就可以直接使用foreach进行遍历了

自己实现Iterable接口

public class TeacherByIterable implements Iterable<Student> {

    private final Student[] students;

    public TeacherByIterable(Student[] students) {
        this.students = students;
    }

    @Override
    public TeacherIterator iterator() {
        return new TeacherIterator();
    }

    private class TeacherIterator implements java.util.Iterator<Student> {

        private int index = 0;

        @Override
        public boolean hasNext() {
            return index < students.length;
        }

        @Override
        public Student next() {
            return students[index++];
        }
    }

    public static void main(String[] args) {
        Student[] students = {new Student("小奏", "18"), new Student("芙莉莲", "2000"), new Student("韩信", "30")};
        TeacherByIterable teacher = new TeacherByIterable(students);
        for (Student student : teacher) {
            System.out.println(student);
        }
    }
}

可以看到自己实现了Iterable接口后,我们可以直接使用foreach进行遍历了。

其次我们也可以自己对迭代遍历进行控制,比如我们可以在TeacherIterator中进行一些逻辑判断,一些数据校验或者处理等

kafka中的Iterable

kafka如果我们进行消费的拉取,我们就可以看到返回的ConsumerRecords就是实现了Iterable接口,可以使用语法糖foreach进行遍历

ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(10000));
for (ConsumerRecord<String, String> record : records) {
    System.out.println("接收到消息:key = " + record.key() + ", value = " + record.value() +
    ", partition = " + record.partition() + ", offset = " + record.offset());
    }
public class ConsumerRecords<K, V> implements Iterable<ConsumerRecord<K, V>>

首先ConsumerRecords实现了Iterable接口, 不同于我们上面的TeacherIterator接口直接实现了Iterator接口

kafka的实现相对来说还要复杂一些

    private static class ConcatenatedIterable<K, V> implements Iterable<ConsumerRecord<K, V>> {

        private final Iterable<? extends Iterable<ConsumerRecord<K, V>>> iterables;

        public ConcatenatedIterable(Iterable<? extends Iterable<ConsumerRecord<K, V>>> iterables) {
            this.iterables = iterables;
        }

        @Override
        public Iterator<ConsumerRecord<K, V>> iterator() {
            return new AbstractIterator<ConsumerRecord<K, V>>() {
                Iterator<? extends Iterable<ConsumerRecord<K, V>>> iters = iterables.iterator();
                Iterator<ConsumerRecord<K, V>> current;

                public ConsumerRecord<K, V> makeNext() {
                    while (current == null || !current.hasNext()) {
                        if (iters.hasNext())
                            current = iters.next().iterator();
                        else
                            return allDone();
                    }
                    return current.next();
                }
            };
        }
    }

kafka先是定义了一个ConcatenatedIterable类,ConcatenatedIterable类继续实现了Iterable,然后再这里返回了一个Iterator对象

不过这里需要注意的是ConcatenatedIterable并没有直接提供实现好的迭代器Iterator,而是通过构造方法传入了一个Iterable(一般是jdk中自己实现的)对象,然后将Iterable对象传入到AbstractIterator中,在AbstractIterator中进行迭代

AbstractIterator这个类实现了Iterator接口,提供了一些默认的实现,然后我们只需要实现makeNext方法就可以了

public abstract class AbstractIterator<T> implements Iterator<T> {

    private enum State {
        READY, NOT_READY, DONE, FAILED
    }

    private State state = State.NOT_READY;
    private T next;

    @Override
    public boolean hasNext() {
        switch (state) {
            case FAILED:
                throw new IllegalStateException("Iterator is in failed state");
            case DONE:
                return false;
            case READY:
                return true;
            default:
                return maybeComputeNext();
        }
    }

    @Override
    public T next() {
        if (!hasNext())
            throw new NoSuchElementException();
        state = State.NOT_READY;
        if (next == null)
            throw new IllegalStateException("Expected item but none found.");
        return next;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("Removal not supported");
    }

    public T peek() {
        if (!hasNext())
            throw new NoSuchElementException();
        return next;
    }

    protected T allDone() {
        state = State.DONE;
        return null;
    }

    protected abstract T makeNext();

    private Boolean maybeComputeNext() {
        state = State.FAILED;
        next = makeNext();
        if (state == State.DONE) {
            return false;
        } else {
            state = State.READY;
            return true;
        }
    }

}

AbstractIteratorkafka中通用的迭代器实现,主要是对jdk中的Iterator进行了一些扩展和优化

具体的优化点如下:

  1. 扩展了State枚举来管理迭代器的内部状态(READY, NOT_READY, DONE, FAILED),让迭代器的行为更加可控和可预测
  2. maybeComputeNext()方法实现了懒加载机制。只有在真正需要下一个元素时才会计算下一个元素,这样可以提高迭代器的性能。
  3. 添加了一些异常处理和方法限制,比如不允许调用remove()方法,没有元数调用next方法时抛出异常自定义异常提示等
  4. 提供了peek()方法,允许查看下一个元素而不消耗它
  5. allDone()方法为子类提供了一种简单的方式来表示迭代结束
  6. 明确不支持remove()操作,避免了不必要的实现复杂性

总结

在一些遍历复杂的业务场景中,我们可以自己实现Iterable接口,然后对整个遍历过程进行管理和监控。同时支持一些更复杂的便利逻辑处理

自定义Iterable接口可以实现延迟加载,处理大数据量的时候不需要一次性将所有数据加载到内存中

比如我们实现一个文件的读取,就可以基于Iterable接口实现,每次读取一行,这样可以避免一次性将所有数据加载到内存中,

public class LazyDataProcessorWithCustomIterator implements Iterable<String> {
    
    private final String filename;

    public LazyDataProcessorWithCustomIterator(String filename) {
        this.filename = filename;
    }

    @Override
    public Iterator<String> iterator() {
        return new LazyFileIterator(filename);
    }

    private static class LazyFileIterator implements Iterator<String> {
        
        private BufferedReader reader;
        
        private String nextLine;

        public LazyFileIterator(String filename) {
            try {
                this.reader = new BufferedReader(new FileReader(filename));
                this.nextLine = reader.readLine();
            } catch (IOException e) {
                throw new RuntimeException("Error initializing file reader", e);
            }
        }

        @Override
        public boolean hasNext() {
            return nextLine != null;
        }

        @Override
        public String next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            String currentLine = nextLine;
            try {
                nextLine = reader.readLine();
                if (nextLine == null) {
                    reader.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("Error reading file", e);
            }
            return currentLine;
        }
    }

    public static void main(String[] args) {
        LazyDataProcessorWithCustomIterator processor = new LazyDataProcessorWithCustomIterator("large_file.txt");
        for (String item : processor) {
            System.out.println(item);
        }
    }
}

同时还可以简化代码,可以实现foreach语法糖,使代码更加简洁和优雅