java8特性之forEach篇

forEach介紹

forEach是java8的特性之一,它能夠大大簡化代碼的操做,好比有關HashMap的操做:java

HashMap<Integer, String> hashMap = new HashMap<>(3);
hashMap.put(1, "張三");
hashMap.put(2, "李四");
hashMap.put(3, "王五");
for (Map.Entry<Integer, String> entry : hashMap.entrySet()) {
    System.out.println(entry.getKey() + "," + entry.getValue());
}
複製代碼

這是使用映射的視圖來遍歷整個hashmap來輸出鍵值對的邏輯,輸出以下:函數

寫起來比較繁瑣,看起來也有點累,那麼使用forEach就能夠簡化爲以下代碼:性能

Map<Integer, String> hashMap = new HashMap<>(3);
hashMap.put(1, "張三");
hashMap.put(2, "李四");
hashMap.put(3, "王五");
hashMap.forEach((k, v) -> System.out.println(k + "," + v));
複製代碼

能夠發現,它簡化了大部分的操做。那麼咱們就會有幾個問題,好比什麼狀況下可使用forEach,以及它的底層迭代原理是什麼,性能跟傳統的foreach相好比何等。測試

使用條件

點進forEach方法中,能夠發現,它是Iterable接口的一個方法,所以能夠得出一個結論,只要一個類實現了此接口,那麼此類的實例必定可使用forEach方法。ui

同時咱們能夠看到,Collection接口繼承了此接口。而咱們大部分的集合類接口都繼承了Collection接口,具體有Set、List、Map、SortedSet、SortedMap、HashSet、TreeSet、ArrayList、LinkedList、 Vector、Collections、Arrays、AbstractCollection。因此只要是上述的實現類,均可以使用forEach方法。this

迭代原理

讓咱們迴歸Iterable接口,看看接口中的方法:spa

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

方法的形參是一個Consumer類型的action,咱們能夠猜到,這必定是跟lambda表達式相關的一個東西。事實上,它是一個函數式接口,讓咱們看看Consumer:3d

@FunctionalInterface
public interface Consumer<T> {
​
    /** * 可實現方法,接受一個參數且沒有返回值 * * @param t the input argument */
    void accept(T t);
​
    /** * 返回一個組合的{@code consumer},該組合的{@code consumer}按順序執行此操做,而後執行 * {@code after}操做。若是執行任一操做時引起異常,則將其中繼到組合操做的調用方。若是執行此操做 * 引起異常,則{@code after}操做將不會執行。 * * @param after the operation to perform after this operation * @return a composed {@code Consumer} that performs in sequence this * operation followed by the {@code after} operation * @throws NullPointerException if {@code after} is null */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
複製代碼

所以實際上,它仍是使用了foreach來遍歷迭代對象,在一個個對參數執行對應的操做。code

性能

爲了測試性能,咱們能夠編寫一個循環,來輸出遍歷完的時間,具體以下:orm

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
​
    for (int i = 0; i < 100000; i++) {
        list.add(i);
    }
​
    long l = System.currentTimeMillis();
    list.forEach(i -> {
    });
    System.out.println(System.currentTimeMillis() - l);
​
    l = System.currentTimeMillis();
    for (Integer s : list) {
    }
    System.out.println(System.currentTimeMillis() - l);
}
複製代碼

輸出結果以下:

大約相差了十五倍。那麼爲何.forEach就會比foreach慢了個十倍左右的數量級呢?細細比較二者區別能夠想到forEach多了一個Consumer的聲明,那麼咱們再來測試一下:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
​
    for (int i = 0; i < 100000; i++) {
        list.add(i);
    }
​
    // 聲明Consumer
    long l = System.currentTimeMillis();
    Consumer<Integer> consumer = integer -> {
    };
    System.out.println(System.currentTimeMillis() - l);
​
    // forEach
    l = System.currentTimeMillis();
    list.forEach(consumer);
    System.out.println(System.currentTimeMillis() - l);
​
    // foreach
    l = System.currentTimeMillis();
    for (Integer integer : list) {
    }
    System.out.println(System.currentTimeMillis() - l);
}
複製代碼

輸出結果以下:

確實能夠得出結論:forEach相比較foreach10倍級的開銷大部分都消耗在了實例化Consumer上,迭代器自己並無什麼區別。

但forEach效率真的這麼低嗎?

其實不是的,java會使用預熱,當第二次第三次調用forEach的時候,速度不比foreach慢,有可能跟JIT有關。

相關文章
相關標籤/搜索