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有關。