Java8 遍历元素 Stream的forEach方法 和 Iterable 接口的forEach方法(及peek方法)

最近看到一个遍历代码的时候,发现里面有一处需要遍历一个集合,对元素进行某种操作(比如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接口提供的,而且两者都是java8的!!此处不得不感谢强大的IDEA,不仅帮我们优化代码,而且帮我们查缺补漏巩固知识点(2019年以后的版本才有自动优化功能)!
点击Replace...之后,IDEA自动将代码为我们优化为:

也就是说,完全没有必要将集合转为stream(),直接调用集合本身Iterable的forEach()即可。

接下来,看两个方法的源码:

  • Iterable接口的forEach():
    default void forEach(Consumer action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
  • stream的forEach():
    void forEach(Consumer action);

两者返回值都为void,都可以接收lambda表达式作为参数。自然,针对上面的需求,用Iterable本身的forEach方法即可,没有必要用流式操作。那么问题来了,什么时候需要用stream的forEach呢?

当然是在对集合进行其他流式操作之后,如:

 students.stream().filter(e -> "男".equals(e.getGender()))
            .forEach(e -> e.setName(e.getName()+"==="));

但,这里就暴露出stream forEach方法的重大缺陷——它的返回值为void,而不是像其他流式操作返回Stream,所以无法进行结果的收集!也就是不能在forEach()之后进行collect,无法获取操作结果的集合!

这就注定了此方法是个鸡肋,因为我们往往是在遍历集合元素进行某种操作之后,要重新收集元素。所以我们不会选用stream的forEach!但问题又来了,由于我们是在使用stream api,本身已经转为stream()了,也不适合再用集合本身Iterable的forEach,那怎么办呢?

答案来了——在stream api里,还提供了一个非常好用,但却知名度很低的方法:peek(),源码如下:
 

    Stream peek(Consumer 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);

你可能感兴趣的:(经验,交流,java)