【java集合梳理】— 淺談iterable接口

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接口繼承了它。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章