foreach
foreach循环是Java5针对数组和集合推出的语法糖,Java8开始融入函数式编程和lambda表达式
先看几个简单的例子
package pers.aslania.thinkinjava.submodule;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
/**
* Created by Aslania on 2018/3/25.
*/
public class ForEach {
public static void traditionalForeach(Collection collection) {
Iterator iterator = collection.iterator();
if (iterator != null) {
for (; iterator.hasNext();)
System.out.println(iterator.next());
}
}
public static void traditionalForeach(Object[] arrays) {
for (int i = 0; i < arrays.length; i++) {
System.out.println(arrays[i]);
}
}
public static void Java5Foreach(Collection collection) {
for (Object o : collection) {
System.out.println(o);
}
}
public static void Java5Foreach(Object[] arrays) {
for (Object o : arrays) {
System.out.println(o);
}
}
public static void Java8Foreach(Collection collection) {
collection.forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
}
public static void Java8ForeachWithLambda(Collection collection) {
collection.forEach(item -> System.out.println(item));
}
public static void main(String[] args) {
Integer[] intArray = new Integer[]{0, 1, 2, 3, 4};
List<Integer> intList = Arrays.asList(intArray);
traditionalForeach(intList);
traditionalForeach(intArray);
Java5Foreach(intList);
Java5Foreach(intArray);
Java8Foreach(intList);
Java8ForeachWithLambda(intList);
}
}
foreach特性总结
- 仅面向数组、实现了Iterable接口的集合
- 无法使用索引
- 无法正常使用return,break,continue等关键字进行流程控制
- foreach所使用的外部变量只能是final变量
- foreach不能改变所遍历元素的值
- 对数组和Java提供的Iterable实现类只能使用正向遍历
foreach深入解析
- foreach循环的核心在于迭代器,遍历的方式取决于迭代器next()方法的实现,所以对Java提供的Iterable实现类只能使用正向遍历
package pers.aslania.thinkinjava.submodule.foreach;
import java.util.Iterator;
/**
* @Author: Aslania
* @Date: Created in 16:37 2018/3/26
* @Description: Reversed
*/
public class ReversedList<E> implements Iterable<E> {
private Node<E> first;
private Node<E> last;
public void add(E element) {
if (last != null) {
last.next = new Node<E>(last, element, null);
last = last.next;
} else {
first = last = new Node<E>(null, element, null);
}
}
/**
* foreach循环的关键,利用集合实现的iterator()方法所提供的迭代器来实现集合的循环遍历
* @return
*/
@Override
public Iterator<E> iterator() {
return new ReversedListIterator(last);
}
private class ReversedListIterator implements Iterator<E> {
private Node<E> current;
public ReversedListIterator(Node<E> current) {
this.current = current;
}
@Override
public boolean hasNext() {
return (null != current) && (null != current.prev);
}
/**
* foreach循环的关键,决定循环遍历的方式,这里采用自定义的反向遍历
* @return
*/
@Override
public E next() {
if (current != null) {
E element = current.item;
current = current.prev;
return element;
}
return null;
}
}
private static class Node<E> {
E item;
ReversedList.Node<E> next;
ReversedList.Node<E> prev;
Node(ReversedList.Node<E> prev, E element, ReversedList.Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
public static void main(String[] args) {
ReversedList<Integer> reversedList = new ReversedList<>();
for (int i = 0; i < 10; i++) {
reversedList.add(i);
}
reversedList.forEach(item -> System.out.println(item));
}
}
///////////////////////////////////////////////
/* Output
9
8
7
6
5
4
3
2
1
*/
///////////////////////////////////////////////
- foreach的实现原理
比较普通的for循环和foreach的代码,可以发现foreach帮开发者简化了很多代码,实际上简化的这部分代码编译器会帮忙补上去。翻阅Iterable接口的foreach方法的默认实现可以发现编译器会将foreach的lambda表达式的内容会以Consume的形式传递给foreach,所以下面Java8Foreach和Java8ForeachWithLambda方法底层代码是相似的
// Iterable.java
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
public static void Java8Foreach(Collection collection) {
collection.forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
}
public static void Java8ForeachWithLambda(Collection collection) {
collection.forEach(item -> System.out.println(item));
}
了解了这一点后,不难理解下面两点:
1. return/break/continue在foreach的lambda中不能起到预期的作用
2. foreach代码块内不能改变外部变量的值,例如下面代码编译会报错
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
int sum = 0;
list.forEach(item -> sum += item); // Compiler error: Variable used in lambda expression should be final or effectively final