journey

iterable接口

整个接口框架关系如下(来自百度百科):

iterable接口其实是java集合大家庭的最顶级的接口之一了,实现这个接口,可以视为拥有了获取迭代器的能力。Iterable接口出现在JDK1.5,那个时候只有iterator()方法,主要是定义了迭代集合内元素的规范。
实现了Iterable接口,我们可以使用增强的for循环,即

for(String str : lists){
     System.out.println(str);
}

1. 内部定义的方法

java集合最源头的接口,实现这个接口的作用主要是集合对象可以通过迭代器去遍历每一个元素。

源码如下:

// 返回一个内部元素为T类型的迭代器(JDK1.5只有这个接口)
Iterator<T> iterator();

// 遍历内部元素,action意思为动作,指可以对每个元素进行操作(JDK1.8添加)
default void forEach(Consumer<? super T> action) {}

// 创建并返回一个可分割迭代器(JDK1.8添加),分割的迭代器主要是提供可以并行遍历元素的迭代器,可以适应现在cpu多核的能力,加快速度。
default Spliterator<T> spliterator() {
    return Spliterators.spliteratorUnknownSize(iterator(), 0);
}

从上面可以看出,foreach迭代以及可分割迭代,都加了default关键字,这个是Java 8 新的关键字,以前接口的所有接口,具体子类都必须实现,而对于deafult关键字标识的方法,其子类可以不用实现,这也是接口规范发生变化的一点。
下面我们分别展示三个接口的调用:

1.1 iterator()方法

iterator()方法,是接口中的核心方法,主要是获取迭代器,获取到的iteratornext(),hasNext(),remove()等方法。

public static void iteratorHasNext(){
    List<String> list=new ArrayList<String>();
    list.add("Jam");
    list.add("Jane");
    list.add("Sam");
    // 返回迭代器
    Iterator<String> iterator=list.iterator();
    // hashNext可以判断是否还有元素
    while(iterator.hasNext()){
        //next()作用是返回当前指针指向的元素,之后将指针移向下个元素
        System.out.println(iterator.next());
    }
}

当然也可以使用for-each loop方式遍历

for (String item : list) {
    System.out.println(item);
}

但是实际上,这种写法在class文件中也是会转成迭代器形式,这只是一个语法糖。class文件如下:

public class IterableTest {
    public IterableTest() { }
    public static void main(String[] args) {
        iteratorHasNext();
    }
    public static void iteratorHasNext() {
        List<String> list = new ArrayList();
        list.add("Jam");
        list.add("Jane");
        list.add("Sam");
        Iterator<String> iterator = list.iterator();
        Iterator var2 = list.iterator();
        while(var2.hasNext()) {
            String item = (String)var2.next();
            System.out.println(item);
        }
    }
}

需要注意的一点是,迭代遍历的时候,如果删除或者添加元素,都会抛出修改异常,这是由于快速失败【fast-fail】机制。

    public static void iteratorHasNext(){
        List<String> list=new ArrayList<String>();
        list.add("Jam");
        list.add("Jane");
        list.add("Sam");
        for (String item : list) {
            if(item.equals("Jam")){
                list.remove(item);
            }
            System.out.println(item);
        }
    }

从下面的错误我们可以看出,第一个元素是有被打印出来的,也就是remove操作是成功的,只是遍历到第二个元素的时候,迭代器检查,发现被改变了,所以抛出了异常。

Jam
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at IterableTest.iteratorHasNext(IterableTest.java:15)
	at IterableTest.main(IterableTest.java:7)

1.2 forEach()方法

其实就是把对每一个元素的操作当成了一个对象传递进来,对每一个元素进行处理。

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

当然像ArrayList自然也是有自己的实现的,那我们就可以使用这样的写法,简洁优雅。forEach方法在java8中参数是java.util.function.Consumer,可以称为消费行为或者说动作类型。

list.forEach(x -> System.out.print(x));

同时,我们只要实现Consumer接口,就可以自定义动作,如果不自定义,默认迭代顺序是按照元素的顺序。

public class ConsumerTest {
    public static void main(String[] args) {
        List<String> list=new ArrayList<String>();
        list.add("Jam");
        list.add("Jane");
        list.add("Sam");
        MyConsumer myConsumer = new MyConsumer();
        Iterator<String> it = list.iterator();
        list.forEach(myConsumer);
    }
    static class MyConsumer implements Consumer<Object> {
        @Override
        public void accept(Object t) {
            System.out.println("自定义打印:" + t);
        }

    }

}

输出的结果:

自定义打印:Jam
自定义打印:Jane
自定义打印:Sam

1.3 spliterator()方法

这是一个为了并行遍历数据元素而设计的迭代方法,返回的是Spliterator,是专门并行遍历的迭代器。以发挥多核时代的处理器性能,java默认在集合框架中提供了一个默认的Spliterator实现,底层也就是Stream.isParallel()实现的,我们可以看一下源码:

    // stream使用的就是spliterator
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
    public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
        Objects.requireNonNull(spliterator);
        return new ReferencePipeline.Head<>(spliterator,
                                            StreamOpFlag.fromCharacteristics(spliterator),
                                            parallel);
    }

使用的方法如下:

    public static void spliterator(){
        List<String> list = Arrays.asList("1", "2", "3","4","5","6","7","8","9","10");
        // 获取可迭代器
        Spliterator<String> spliterator = list.spliterator();
        // 一个一个遍历
        System.out.println("tryAdvance: ");
        spliterator.tryAdvance(item->System.out.print(item+" "));
        spliterator.tryAdvance(item->System.out.print(item+" "));
        System.out.println("\n-------------------------------------------");

        // 依次遍历剩下的
        System.out.println("forEachRemaining: ");
        spliterator.forEachRemaining(item->System.out.print(item+" "));
        System.out.println("\n------------------------------------------");

        // spliterator1:0~10
        Spliterator<String> spliterator1 = list.spliterator();
        // spliterator1:6~10 spliterator2:0~5
        Spliterator<String> spliterator2 = spliterator1.trySplit();
        // spliterator1:8~10 spliterator3:6~7
        Spliterator<String> spliterator3 = spliterator1.trySplit();
        System.out.println("spliterator1: ");
        spliterator1.forEachRemaining(item->System.out.print(item+" "));
        System.out.println("\n------------------------------------------");
        System.out.println("spliterator2: ");
        spliterator2.forEachRemaining(item->System.out.print(item+" "));
        System.out.println("\n------------------------------------------");
        System.out.println("spliterator3: ");
        spliterator3.forEachRemaining(item->System.out.print(item+" "));
    }
  • tryAdvance() 一个一个元素进行遍历
  • forEachRemaining() 顺序地分块遍历
  • trySplit()进行分区形成另外的 Spliterator,使用在并行操作中,分出来的是前面一半,就是不断把前面一部分分出来

结果如下:

tryAdvance: 
1 2 
-------------------------------------------
forEachRemaining: 
3 4 5 6 7 8 9 10 
------------------------------------------
spliterator1: 
8 9 10 
------------------------------------------
spliterator2: 
1 2 3 4 5 
------------------------------------------
spliterator3: 
6 7 

还有一些其他的用法在这里就不列举了,主要是trySplit()之后,可以用于多线程遍历。理想的时候,可以平均分成两半,有利于并行计算,但是不是一定平分的。

总结

以上可以得知,iterable接口,主要是定义了迭代遍历的规范,这个接口的作用是获取迭代器,迭代器在JDK1.8版本增加了可分割迭代器,更有利于并发处理。iterable接口,从字面意义来说,就是可以迭代的意思,可以理解为实现这个接口的集合类获得了迭代遍历的能力,同时它也是集合的顶级接口,Collection接口继承了它。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

纵然缓慢,驰而不息。