最近看到一个遍历代码的时候,发现里面有一处需要遍历一个集合,对元素进行某种操作(比如set某个属性),此处用的是map(),在map方法里返回一个更新后的元素。而对于此功能,自己首先想到的是forEach()方法,因为在我之前的概念里,forEach()方法才是用来遍历操作的,而map是用来将一个类型的集合映射为另一个类型的集合(当然,映射为同一个类型也无可厚非)。于是自己详细测试了forEach方法,发现这里面还是有些门道的!下面详说:
List students = new LinkedList<>();
students.add(new Student(3,"张三", "男"));
students.add(new Student(2,"李四", "女"));
students.add(new Student(8,"王五", "男"));
students.add(new Student(5,"赵六", "女"));
students.add(new Student(4,"田七", "女"));
students.add(new Student(6,"郭八", "男"));
有一个Student的集合,现在要遍历该集合,修改每一个Student的名字。
首先,用最先想到的stream提供的forEach()方法:
students.stream().forEach(e -> e.setName(e.getName()+"==="));
本以为这就是最好的答案,结果,IDEA在forEach上标黄,提示这个forEach()方法可以被替换为Iterable接口的forEach()。直到这时我才意识到,原来有两个同名的forEach()方法,一个是stream api提供的,另一个是Iterable
点击Replace...之后,IDEA自动将代码为我们优化为:
也就是说,完全没有必要将集合转为stream(),直接调用集合本身Iterable
接下来,看两个方法的源码:
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
void forEach(Consumer super T> action);
两者返回值都为void,都可以接收lambda表达式作为参数。自然,针对上面的需求,用Iterable本身的forEach方法即可,没有必要用流式操作。那么问题来了,什么时候需要用stream的forEach呢?
当然是在对集合进行其他流式操作之后,如:
students.stream().filter(e -> "男".equals(e.getGender()))
.forEach(e -> e.setName(e.getName()+"==="));
但,这里就暴露出stream forEach方法的重大缺陷——它的返回值为void,而不是像其他流式操作返回Stream
这就注定了此方法是个鸡肋,因为我们往往是在遍历集合元素进行某种操作之后,要重新收集元素。所以我们不会选用stream的forEach!但问题又来了,由于我们是在使用stream api,本身已经转为stream()了,也不适合再用集合本身Iterable的forEach,那怎么办呢?
答案来了——在stream api里,还提供了一个非常好用,但却知名度很低的方法:peek(),源码如下:
Stream peek(Consumer super T> action);
可见它也适合用来遍历集合进行相关操作,重点是它返回Stream
List result = students.stream().filter(e -> "男".equals(e.getGender()))
.peek(e -> e.setName(e.getName() + "==="))
.sorted(Comparator.comparingInt(Student::getId))
.collect(Collectors.toList());
System.out.println(result);
打印结果正确无误(已排序):
[Student{id=3, name='张三=========', gender='男'}, Student{id=6, name='郭八=========', gender='男'}, Student{id=8, name='王五=========', gender='男'}]
所以,回到最开始的问题,对于遍历一个集合,对元素进行修改属性等操作,还想得到操作结果,此时用map()虽然能实现,但思路还是不太正确的,最好使用更加适合的peek()方法。
List students = new LinkedList<>();
students.add(new Student(3,"张三", "男"));
students.add(new Student(2,"李四", "女"));
students.add(new Student(8,"王五", "男"));
students.add(new Student(5,"赵六", "女"));
students.add(new Student(4,"田七", "女"));
students.add(new Student(6,"郭八", "男"));
// 如果只是想遍历集合进行某种操作,用stream的forEach是多余的,直接用Iterable的forEach即可!
students.stream().forEach(e -> e.setName(e.getName()+"==="));
// stream的forEach用在已经对集合进行其他流失操作之后,但缺陷是无返回值,无法collect!
students.stream().filter(e -> "男".equals(e.getGender()))
.forEach(e -> e.setName(e.getName()+"==="));
// 要想遍历集合进行相关操作,又想收集结果,就用peek方法!
List result = students.stream().filter(e -> "男".equals(e.getGender()))
.peek(e -> e.setName(e.getName() + "==="))
.sorted(Comparator.comparingInt(Student::getId))
.collect(Collectors.toList());
System.out.println(result);