Iterable源碼分析
(一)簡介
Iterable是從jdk1.5就存在的接口,稱爲內部迭代器,常用作容器類的接口,以支持遍歷操作(同時支持流式遍歷)
內部迭代器的特點是嵌入,其迭代行爲必須在容器對象內部實現(藉助了外部比較器)。一個類如果實現了Iterable接口,就意味着“該類本身支持遍歷”,並可以通過for-each這種循環語法來直接遍歷。當然,一個類如果沒有實現Iterable接口,也可以通過掛載外部迭代器Iterator進行遍歷。
此外,內部迭代器還可轉換爲可分割迭代器Spliterator,以便用於流式操作
注意區別於外部迭代器Iterator和枚舉器Enumeration
它就只有三個API。
// since 1.5
Iterator<T> iterator();
// since 1.8
default void forEach(Consumer<? super T> action){}
// since 1.8
default Spliterator<T> spliterator(){}
(二)源碼分析
😪 首先看第一個API,它的功能很簡單就是返回T元素類型的迭代器
// 返回T元素類型的迭代器
Iterator<T> iterator();
😏 再看第二個APi
// 對Iterable的每個元素執行給定操作(函數式接口),直到處理完所有元素或操作引發異常。
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
Consumer 是用得非常多的函數式接口
@FunctionalInterface
public interface Consumer<T>
所謂的函數式接口,是在接口裏面只能有一個抽象方法。這種類型的接口也稱爲SAM接口,即Single Abstract Method
interfaces。
它們主要用在Lambda表達式和方法引用(實際上也可認爲是Lambda表達式)上。
比如說定義了一個函數式接口如下:
@FunctionalInterface
interface MakeFood{
void doSomething();
}
那麼就可以使用Lambda表達式來表示該接口的一個實現(注:JAVA 8 之前一般是用匿名類實現的)
MakeFood makeFood = ()-> System.out.println("蒸包子");
你會注意到定義函數式接口的時候有一個註解 @FunctionalInterface
這是Java 8爲函數式接口引入的新註解,主要用於編譯級錯誤檢查,加上該註解,當你寫的接口不符合函數式接口定義的時候,編譯器會報錯。
比如說接口中包含了兩個抽象方法,這就違反了函數式接口的定義,一般IDE會提示你。
在Java 8 中內置內置四大核心函數式接口,第一個就是Consumer
演示一下:
import org.junit.Test;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/*
* Java8 內置的四大核心函數式接口
*
* Consumer<T> : 消費型接口
* void accept(T t);
*
* Supplier<T> : 供給型接口
* T get();
*
* Function<T, R> : 函數型接口
* R apply(T t);
*
* Predicate<T> : 斷言型接口
* boolean test(T t);
*
*/
public class MyTest {
//Predicate<T> 斷言型接口:
@Test
public void test4(){
List<String> list = Arrays.asList("Hello", "lala", "Lambda", "www", "ok");
List<String> strList = filterStr(list, (s) -> s.length() > 3);
for (String str : strList) {
System.out.println(str);
}
}
//需求:將滿足條件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();
for (String str : list) {
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}
//Function<T, R> 函數型接口:
@Test
public void test3(){
String newStr = strHandler("\t\t\t 我和我的祖國 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("一刻也不能分離", (str) -> str.substring(2, 5));
System.out.println(subStr);
}
//需求:用於處理字符串
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}
//Supplier<T> 供給型接口 :
@Test
public void test2(){
List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//需求:產生指定個數的整數,並放入集合中
private List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
//Consumer<T> 消費型接口 :
@Test
public void test1(){
happy(10000, (m) -> System.out.println("吃喝玩樂花了" + m + "元"));
}
private void happy(double money, Consumer<Double> con){
con.accept(money);
}
}
相比說到這裏你就明白了,我們可以通過函數式接口實現 遍歷每個元素時,並對其執行相應的擇取操作。
至於Lambda表達式的詳細內容可以參考我這篇文章【Java 8 in Action】Lambda表達式
我們看一個該方法的一個落地點 – ArrayList中對forEach進行了實現
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
以循環打印Arraylist中的值爲例,在java8之前的寫法是
for(Integer i : list) {
System.out.println(i);
}
在java8中可以寫成
list.forEach(x -> System.out.print(x));
比之前舒服多了,可是雖然forEach使代碼看上去更簡潔,但是從效率上看卻是相反的,最原始的循環效率最高,操作越方便的反而性能會下降,操作越方便的方法其內部都是層層調用
不過list.stream().forEach支持多線程並行操作,這樣子的話,在項目中效率反而可能會提升。
😔 最後一個API
// 關注 Spliterator接口
// 將普通迭代器轉換爲可分割迭代器,用於流式操作
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
Spliterator(splitable iterator可分割迭代器)接口是Java爲了並行遍歷數據源中的元素而設計的迭代器,這個可以類比順序遍歷迭代器Iterator,但一個是順序遍歷,一個是並行遍歷。