Java 集合教程

Java 集合:Iterable 接口

Iterable 接口表示一个可迭代的对象集合,其核心含义是 “支持迭代操作”。这意味着,任何实现了 Iterable 接口的类,都具备被遍历的能力,能够通过迭代器(Iterator)依次访问其包含的元素,这也是集合类支持 for-each 循环的基础。

迭代 Iterable

使用 for-each 循环迭代 Iterable

迭代 Iterable 元素的第一种方法是使用 for-each 循环。下面示例展示了如何通过 for-each 循环遍历 List 的元素。由于 List 接口扩展了 Collection 接口,而 Collection 接口扩展了 Iterable 接口,因此 List 对象可以与 for-each 循环一起使用。

Java 集合:Iterable 接口

示例代码:

import java.util.*;

public class Demo {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        
        for( String element : list ){
            System.out.println( element.toString() );
        }
    }
    
}

运行结果:

one
two
three

此示例首先创建一个新的列表并向其中添加 3 个元素。然后,它使用增强 for 循环来迭代列表的元素,并打印出每个元素的 toString() 值。

使用 Iterator 迭代 Iterable

遍历 Iterable 元素的第二种方法是通过调用 Iterable 的 iterator() 方法从中获取一个 Iterator。然后,你可以像使用其他迭代器一样遍历该迭代器。例如:

import java.util.*;

public class Demo {
  
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("one");
    list.add("two");
    list.add("three");
    
    Iterator<String> iterator = list.iterator();
    while(iterator.hasNext()) {
        String element = iterator.next();
        System.out.println( element );
    }
  }
  
}

运行结果:

one
two
three

使用 forEach() 方法迭代 Iterable

遍历 Iterable 元素的第三种方法是使用 forEach() 方法。forEach()方法将 lambda 表达式作为参数。迭代表中的每个元素都会调用一次这个 lambda 表达式。例如:

import java.util.*;

public class Demo {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");

        // 迭代
        list.forEach( (element) -> {
            System.out.println( element );
        });
    }
    
}

运行结果:

one
two
three

  

Iterable 接口定义

Iterable 接口有三个方法,其中只有一个需要实现,另外两个方法有默认实现。下面是 Iterable 接口的定义:

package java.lang;

import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;

/**
 * 实现此接口的对象可以作为增强型for循环(有时称为"for-each循环")的目标。
 * 它是Java集合框架中迭代操作的基础接口,提供了统一的元素遍历方式。
 * 
 * <p>任何实现了Iterable接口的类都可以被迭代,这使得客户端代码可以使用一致的方式
 * 遍历不同类型的集合,而无需关心其内部实现细节。
 *
 * @param <T> 迭代器返回的元素类型
 *
 * @since 1.5 (Java 5引入)
 * @jls 14.14.2 增强型for语句规范
 */
public interface Iterable<T> {
    /**
     * 返回一个用于遍历T类型元素的迭代器。
     * 迭代器提供了hasNext()和next()等方法,支持对集合元素的顺序访问。
     *
     * @return 用于遍历元素的Iterator实例
     */
    Iterator<T> iterator();

    /**
     * 对Iterable中的每个元素执行给定的操作,直到所有元素都被处理或操作抛出异常为止。
     * 操作按照迭代顺序执行(如果指定了迭代顺序)。操作抛出的异常会直接传递给调用者。
     *
     * <p>如果操作执行了修改底层元素源的副作用,此方法的行为是未指定的,
     * 除非覆盖此类指定了并发修改策略。
     *
     * @implSpec (实现规范)
     * <p>默认实现的行为等同于:
     * <pre>{@code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action 要对每个元素执行的操作
     * @throws NullPointerException 如果指定的action为null
     * @since 1.8 (Java 8引入,支持函数式编程)
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);  // 确保操作不为null,否则抛出异常
        for (T t : this) {               // 使用增强for循环遍历所有元素
            action.accept(t);            // 对每个元素执行消费操作
        }
    }

    /**
     * 为当前Iterable描述的元素创建一个Spliterator(可分割迭代器)。
     * Spliterator支持并行遍历,是Java 8中为了支持集合并行处理而引入的接口。
     *
     * @implSpec (实现规范)
     * 默认实现从Iterable的迭代器创建一个"早期绑定"的spliterator。
     * 该spliterator继承了Iterable迭代器的"快速失败"特性(当检测到并发修改时抛出异常)。
     *
     * @implNote (实现注意事项)
     * 默认实现通常应该被重写。默认实现返回的spliterator具有较差的分割能力,
     * 未指定大小,并且不报告任何spliterator特性。实现类几乎总能提供更好的实现。
     *
     * @return 用于遍历当前Iterable元素的Spliterator
     * @since 1.8 (Java 8引入,支持并行流操作)
     */
    default Spliterator<T> spliterator() {
        // 创建一个未知大小的spliterator,使用当前Iterable的迭代器作为数据源
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

注意,你必须实现的方法名为 iterator()。该方法必须返回一个 Iterator,用于遍历实现 Iterable 接口的对象中的元素。获取迭代器的过程是在幕后进行的,所以你看不到这个过程。当您在 for-each 循环中使用 Iterable 时,Java 编译器会负责生成相关代码。

  

Java 中 Iterable 的实现

Iterable 接口是 Java 集合框架的根接口之一,Java 中有多个类实现了 Iterable 接口。因此,这些类可以迭代其内部元素。

还有几个 Java 接口扩展了 Iterable 接口。因此,实现了 Iterable 扩展接口的类也实现了 Iterable 接口。这些类也可以迭代其元素。

Collection 接口扩展了 Iterable,因此 Collection 的所有子类也实现了 Iterable 接口。例如,Java 的 List 和 Set 接口都扩展了 Collection 接口,从而也扩展了 Iterable 接口。如下图:

Java 集合:Iterable 接口

Java 集合:Iterable 接口

  

实现 Iterable 接口

要实现 Iterable 接口以便使用 for-each 循环,只需完成两个核心步骤:

(1)实现 Iterable 接口并提供 iterator() 方法。

(2)实现对应的 Iterator 接口来定义遍历逻辑。

以下是一个完整示例,展示如何自定义一个可迭代的集合类:

import java.util.Iterator;

// 自定义可迭代的集合类
class MyCollection<T> implements Iterable<T> {
    private T[] elements;
    private int size;

    // 构造方法初始化集合
    @SuppressWarnings("unchecked")
    public MyCollection(int capacity) {
        elements = (T[]) new Object[capacity];
        size = 0;
    }

    // 添加元素方法
    public void add(T element) {
        if (size < elements.length) {
            elements[size++] = element;
        }
    }

    // 实现Iterable接口的核心方法,返回迭代器
    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    // 利用内部类实现 Iterator 接口
    private class MyIterator implements Iterator<T> {
        private int currentIndex = 0; // 跟踪当前迭代位置

        // 判断是否还有下一个元素
        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        // 获取下一个元素并移动指针
        @Override
        public T next() {
            return elements[currentIndex++];
        }
    }
}

public class CustomIterableExample {
    public static void main(String[] args) {
        // 创建自定义集合并添加元素
        MyCollection<String> collection = new MyCollection<>(3);
        collection.add("Apple");
        collection.add("Banana");
        collection.add("Cherry");

        // 使用for-each循环遍历(得益于实现了Iterable接口)
        for (String fruit : collection) {
            System.out.println(fruit);
        }
    }
}

运行结果:

Apple
Banana
Cherry

注意,迭代器的状态(如 currentIndex)应该封装在 Iterator 实现中,而非 Iterable 类中,这样支持同时创建多个独立的迭代器。如果需要支持移除元素,可以在 Iterator 中重写 remove() 方法,Java 8+ 中还可以选择性重写 forEach() 方法,提供更灵活的遍历方式。

  

获取 Spliterator

可以通过 spliterator() 方法从 Iterable 获取 Spliterator。例如:

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

public class SpliteratorDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        list.add("four");
        list.add("five");

        // 获取列表的Spliterator
        Spliterator<String> spliterator = list.spliterator();
        System.out.println("=== 1. 遍历所有元素 ===");
        // 使用forEachRemaining遍历元素
        spliterator.forEachRemaining(element -> System.out.println(element));

        // 重新获取Spliterator
        spliterator = list.spliterator();
        System.out.println("\n=== 2. 尝试分割Spliterator ===");
        // 尝试分割为两个Spliterator
        Spliterator<String> secondSpliterator = spliterator.trySplit();
        if (secondSpliterator != null) {
            System.out.println("--- 第一个部分的元素 ---");
            spliterator.forEachRemaining(System.out::println);
            
            System.out.println("--- 第二个部分的元素 ---");
            secondSpliterator.forEachRemaining(System.out::println);
        }

        // 重新获取Spliterator
        spliterator = list.spliterator();
        System.out.println("\n=== 3. 特性与估计大小 ===");
        // 查看Spliterator的特性
        int characteristics = spliterator.characteristics();
        System.out.println("是否有序: " + ((characteristics & Spliterator.ORDERED) != 0));
        System.out.println("是否有大小: " + ((characteristics & Spliterator.SIZED) != 0));
        System.out.println("估计元素数量: " + spliterator.estimateSize());

        System.out.println("\n=== 4. 逐个处理元素 ===");
        // 逐个处理元素
        while (spliterator.tryAdvance(element -> System.out.println("处理元素: " + element))) {
            // tryAdvance返回true表示还有元素可处理
        }
    }
}

运行结果:

=== 1. 遍历所有元素 ===
one
two
three
four
five

=== 2. 尝试分割Spliterator ===
--- 第一个部分的元素 ---
three
four
five
--- 第二个部分的元素 ---
one
two

=== 3. 特性与估计大小 ===
是否有序: true
是否有大小: true
估计元素数量: 5

=== 4. 逐个处理元素 ===
处理元素: one
处理元素: two
处理元素: three
处理元素: four
处理元素: five

  

Iterable 性能

如果你编写的代码需要在一个循环中多次迭代一个集合,比方说每秒迭代一个 List 上千次,那么通过 for-each 进行循环迭代 List 要比通过标准 for 循环迭代 List 慢,示例代码:

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

public class IterablePerformance {

    public static void main(String[] args) {
        // 初始化一个大小为 1 万的 list
        List<String> list = new ArrayList<>();
        for(int i = 0; i < 10000; i++) {
            list.add(String.valueOf(System.currentTimeMillis()));
        }

        // 通过传统方式遍历10000次
        long s1 = System.currentTimeMillis();
        for(int n = 0; n < 10000; n++) {
            for(int i = 0; i < list.size(); i++) {
                String val = list.get(i);
            }
        }
        System.out.println("传统方式耗时:" + (System.currentTimeMillis() - s1));

        // 通过for-each方式遍历10000次
        long s2 = System.currentTimeMillis();
        for(int n = 0; n < 10000; n++) {
            for(String str : list) {
                //
            }
        }
        System.out.println("for-each方式耗时:" + (System.currentTimeMillis() - s2));
    }

}

运行结果:

传统方式耗时:134
for-each方式耗时:262

注意,for-each 循环速度较慢的原因是,每次迭代都会调用 iterator() 方法,从而创建一个新的 Iterator 对象。与使用标准 for 循环迭代 List 相比,每秒创建一个新对象数千次,甚至数百万次,确实会对性能造成一定影响。对于偶尔迭代集合的大多数标准业务应用程序来说,这种性能差异无关紧要。只有在每秒执行数千次的非常紧凑的循环中才会出现这种情况。

  

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号