Stream流的中间操作和终端操作

打印 上一主题 下一主题

主题 945|帖子 945|积分 2835

最近在写代码时发现一个很有意思的问题
问题代码:
  1. 1 // 1.准备一个集合,排序。
  2. 2         List<Movie> movies = new ArrayList<>();
  3. 3         movies.add(new Movie("摔跤吧,爸爸", 9.5, "阿米尔汗"));
  4. 4         movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
  5. 5         movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
  6. 6         movies.add(new Movie("阿甘正传", 7.5, "汤姆汉克斯"));
  7. 7 // map加工方法(映射):把流上的数据加工成新数据。
  8. 8         System.out.println("-----------------------------------------------");
  9. 9         //第一次map
  10. 10         movies.stream().map( movie-> {
  11. 11                 movie.setName("电影:"+movie.getName());
  12. 12                 return movie;
  13. 13             }
  14. 14         );
  15. 15         //第二次map 加 foreach
  16. 16         movies.stream().map( m -> {
  17. 17                 m.setName("黑马:" + m.getName());
  18. 18                 return m;
  19. 19         }).forEach(System.out::println);
  20. 20 21
  21. 22         System.out.println("原始数据" +movies);
复制代码
输出结果:
  1. Movie{name='黑马:摔跤吧,爸爸', score=9.5, actor='阿米尔汗'}
  2. Movie{name='黑马:三傻宝莱坞', score=8.5, actor='阿米尔汗2'}
  3. Movie{name='黑马:三傻宝莱坞', score=8.5, actor='阿米尔汗2'}
  4. Movie{name='黑马:阿甘正传', score=7.5, actor='汤姆汉克斯'}
  5. 原始数据[Movie{name='黑马:摔跤吧,爸爸', score=9.5, actor='阿米尔汗'}, Movie{name='黑马:三傻宝莱坞', score=8.5, actor='阿米尔汗2'}, Movie{name='黑马:三傻宝莱坞', score=8.5, actor='阿米尔汗2'}, Movie{name='黑马:阿甘正传', score=7.5, actor='汤姆汉克斯'}]
复制代码
 

  • 第一个map()方法中没有使用collect()方法来收集加工后的流,而是直接调用了第二个map()方法。这样会导致第一个map()方法的结果被丢弃,可为什么第二个map()方法加上forEach会改变原始数据呢?
为了弄明白其中缘由我查询了一些资料,究其原因和Stream流的中间操作和终端操作有关
在Java 8中,stream是一种抽象的数据结构,它表示一个元素序列,可以对这些元素进行各种操作,比如过滤、映射、排序、聚合等。stream本身并不存储数据,而是从一个源(比如集合、数组、文件等)获取数据,并按照一定的规则处理数据,然后输出到一个目标(比如另一个集合、数组、文件等)。
stream有两种类型的操作:中间操作和终端操作。中间操作是指返回一个新的stream的操作,比如map、filter、sorted等。终端操作是指返回一个非stream的结果的操作,比如forEach、collect、reduce等。
当我们对一个stream进行中间操作时,并不会立即执行这些操作,而是会创建一个新的stream,并记录下这些操作。只有当我们对这个stream进行终端操作时,才会触发这些中间操作的执行,这种机制称为惰性求值
例如,当我们写下以下代码时: List list = Arrays.asList(1, 2, 3, 4, 5);  list.stream().map(i -> i * 2); 
并不会立即对list中的每个元素乘以2,而是会返回一个新的stream,并记录下map这个中间操作。只有当我们对这个stream进行终端操作时,比如:
  1. List<Integer> newList = list.stream().map(i -> i * 2).collect(Collectors.toList());
复制代码
才会触发map这个中间操作的执行,并把结果收集到一个新的列表中。
那么,为什么在后面调用forEach也可以保存修改的对象呢?这是因为forEach是一种特殊的终端操作,它不会返回任何结果,而是对stream中的每个元素执行一个消费者函数(Consumer),这个函数可以对元素进行任何操作,包括修改元素的状态。
例如,当我们写下以下代码时:
  1. List<SampleDTO> list = ...; // 假设list是一个SampleDTO对象的列表
  2. list.stream().forEach(s -> s.setText(s.getText() + "xxx")); // 对每个对象的text属性追加"xxx"
复制代码
就会触发forEach这个终端操作的执行,并对list中的每个对象执行消费者函数s -> s.setText(s.getText() + “xxx”),这个函数会修改对象的text属性。因此,在执行完这段代码后,list中的每个对象都会被修改。
需要注意的是,虽然forEach可以修改对象的状态,但并不意味着它可以修改stream的源。例如,以下代码是错误的:
  1. List<SampleDTO> list = ...; // 假设list是一个SampleDTO对象的列表
  2. list.stream().forEach(s -> list.remove(s)); // 尝试删除每个对象
复制代码
这段代码会抛出ConcurrentModificationException异常,因为它试图在遍历list的同时修改list,这是不允许的。
 总之,当我们在后面调用forEach也可以保存修改的对象,是因为forEach是一种特殊的终端操作,它可以对stream中的每个元素执行任何操作,包括修改元素的状态。但是,我们应该避免使用forEach来修改元素的状态,因为这样会破坏函数式编程的原则和可读性。我们应该尽量使用其他终端操作来返回新的结果,而不是修改原来的结果。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

傲渊山岳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表