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

最近看到一个遍历代码的时候,发现里面有一处需要遍历一个集合,对元素进行某种操作(比如set某个属性),此处用的是map(),在map方法里返回一个更新后的元素。而对于此功能,自己首先想到的是forEach()方法,因为在我之前的概念里,forEach()方法才是用来遍历操作的,而map是用来将一个类型的集合映射为另一个类型的集合(当然,映射为同一个类型也无可厚非)。于是自己详细测试了forEach方法,发现这里面还是有些门道的!下面详说:
 

List<Student> 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<T>接口提供的,而且两者都是java8的!!此处不得不感谢强大的IDEA,不仅帮我们优化代码,而且帮我们查缺补漏巩固知识点(2019年以后的版本才有自动优化功能)!
点击Replace...之后,IDEA自动将代码为我们优化为:

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

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

  • Iterable<T>接口的forEach():
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
  • stream的forEach():
    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<T>,所以无法进行结果的收集!也就是不能在forEach()之后进行collect,无法获取操作结果的集合!

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

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

    Stream<T> peek(Consumer<? super T> action);

可见它也适合用来遍历集合进行相关操作,重点是它返回Stream<T>,这样就可以继续进行其他中间或终结性操作了,如:

List<Student> 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<Student> 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<Student> 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);

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

国产野马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值