最近在写代码时发现一个很有意思的问题
问题代码:- 1 // 1.准备一个集合,排序。
- 2 List<Movie> movies = new ArrayList<>();
- 3 movies.add(new Movie("摔跤吧,爸爸", 9.5, "阿米尔汗"));
- 4 movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
- 5 movies.add(new Movie("三傻宝莱坞", 8.5, "阿米尔汗2"));
- 6 movies.add(new Movie("阿甘正传", 7.5, "汤姆汉克斯"));
- 7 // map加工方法(映射):把流上的数据加工成新数据。
- 8 System.out.println("-----------------------------------------------");
- 9 //第一次map
- 10 movies.stream().map( movie-> {
- 11 movie.setName("电影:"+movie.getName());
- 12 return movie;
- 13 }
- 14 );
- 15 //第二次map 加 foreach
- 16 movies.stream().map( m -> {
- 17 m.setName("黑马:" + m.getName());
- 18 return m;
- 19 }).forEach(System.out::println);
- 20 21
- 22 System.out.println("原始数据" +movies);
复制代码 输出结果:- 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='汤姆汉克斯'}
- 原始数据[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进行终端操作时,比如:- List<Integer> newList = list.stream().map(i -> i * 2).collect(Collectors.toList());
复制代码 才会触发map这个中间操作的执行,并把结果收集到一个新的列表中。
那么,为什么在后面调用forEach也可以保存修改的对象呢?这是因为forEach是一种特殊的终端操作,它不会返回任何结果,而是对stream中的每个元素执行一个消费者函数(Consumer),这个函数可以对元素进行任何操作,包括修改元素的状态。
例如,当我们写下以下代码时:- List<SampleDTO> list = ...; // 假设list是一个SampleDTO对象的列表
- list.stream().forEach(s -> s.setText(s.getText() + "xxx")); // 对每个对象的text属性追加"xxx"
复制代码 就会触发forEach这个终端操作的执行,并对list中的每个对象执行消费者函数s -> s.setText(s.getText() + “xxx”),这个函数会修改对象的text属性。因此,在执行完这段代码后,list中的每个对象都会被修改。
需要注意的是,虽然forEach可以修改对象的状态,但并不意味着它可以修改stream的源。例如,以下代码是错误的:- List<SampleDTO> list = ...; // 假设list是一个SampleDTO对象的列表
- list.stream().forEach(s -> list.remove(s)); // 尝试删除每个对象
复制代码 这段代码会抛出ConcurrentModificationException异常,因为它试图在遍历list的同时修改list,这是不允许的。
总之,当我们在后面调用forEach也可以保存修改的对象,是因为forEach是一种特殊的终端操作,它可以对stream中的每个元素执行任何操作,包括修改元素的状态。但是,我们应该避免使用forEach来修改元素的状态,因为这样会破坏函数式编程的原则和可读性。我们应该尽量使用其他终端操作来返回新的结果,而不是修改原来的结果。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |