当前位置: 首页 > 科技观察

请避开JavaStream编程的常见陷阱

时间:2023-03-18 15:20:06 科技观察

Java8由Oracle于2014年发布,是继Java5之后最具革命性的版本。Java8吸取了其他语言的精华,带来了函数式编程、lambda表达式、流等一系列新特性。学习了这些新特性后,你就可以实现高效的编码和优雅的编码。1.什么是流?Stream是Java8中的一个新接口,它允许以声明方式处理数据集合。Stream不是集合类型,不保存数据。它可以看作是一种高级迭代器(Iterator),用于遍历数据集合。Stream操作可以像Builder一样一步步堆叠起来,形成一个流水线。一个管道一般由一个数据源+零个或多个中间操作+一个终端操作组成。中间操作可以将一个流转换为另一个流,例如使用filter过滤元素,使用map提取值。Stream和lambda表达式是密不可分的。本文假定您已经掌握了lambda的基础知识。2、Stream的特性只能被遍历(消费)一次。Stream实例只能遍历一次,结束操作后遍历结束,再次遍历需要重新生成实例,类似于Iterator迭代器。保护数据源。对Stream中的任何元素的修改都不会导致数据源被修改。比如过滤删除流中的一个元素,仍然可以通过再次遍历数据源得到该元素。懒惰的。filter和map操作连接起来形成一系列中间操作,如果没有终端操作(例如collect),这些操作永远不会执行。3.创建Stream实例的方法(1)使用指定值创建Stream实例//of是Stream的静态方法StreamstrStream=Stream.of("hello","java8","stream");//或者使用基本类型流IntStreamintStream=IntStream.of(1,2,3);复制代码(2)使用集合创建Stream实例(常用方式)//使用guava库初始化一个不可变列表对象ImmutableListintegers=ImmutableList.of(1,2,3);//List接口继承Collection接口,java8在Collection接口中增加了stream方法Streamstream=integers.stream();复制代码(3)CreateStreaminstanceusingarray//InitializeanarrayInteger[]array={1,2,3};//使用Arrays的静态方法streamStreamstream=Arrays.stream(array);复制代码(4)使用generator创建Stream实例//随机生成100个整数Randomrandom=newRandom();//加上限制,否则会无限流Streamstream=Stream.generate(random::nextInt).限制(100);复制代码(5)使用迭代设备创建一个Stream实例//生成100个奇数,加上limit,否则就是无限流Streamstream=Stream.iterate(1,n->n+2)。限制(100);stream.forEach(System.out::println);复制代码(6)使用IO接口创建Stream实例//获取指定路径下的文件信息,list方法返回Stream类型StreampathStream=Files.list(Paths.get("/"));复制代码4.stream常用操作的Stream接口中定义了很多操作,大致可以分为两类,一类是中间操作,一类是终端操作;(1)中间操作中间操作会返回另一个流,多个中间操作可以连接起来形成查询中间操作是惰性的。如果流上没有终端操作,则中间操作不会做任何处理。下面介绍常用的中间操作:map操作map是将输入流中的每个元素映射到另一个元素,形成输出流。//初始化一个不可变字符串Listwords=ImmutableList.of("hello","java8","stream");//计算列表中每个单词的长度Listlist=words.stream().map(String::length).collect(Collectors.toList());//output:556list.forEach(System.out::println);复制代码flatMap操作Listlist1=words。stream().map(word->word.split("-")).collect(Collectors.toList());//输出:[Ljava.lang.String;@59f95c5d,//[Ljava.lang.String;@5ccd43c2list1.forEach(System.out::println);复制代码在哪里?你期望的是一个List,但是返回的是一个List,这是因为split方法返回的是一个String[]这时候你可以想到把数组转成流,于是就有了第二个版本的Stream>arrStream=words.stream().map(word->word.split("-")).map(Arrays::stream);//输出:java.util.stream.ReferencePipeline$Head@2c13da15,//java.util.stream.ReferencePipeline$Head@77556fdarrStream.forEach(System.out::println);复制代码还是出错,这个问题可以用flatMap平流解决,flatMap把流中的每一个元素取出来,变成另一个输出流StreamstrStream=words.stream().map(word->word.split("-")).flatMap(Arrays::stream).distinct();//output:hellojava8streamstrStream.forEach(System.out::println);复制代码filter操作filter接收Predicate对象,根据条件过滤器,满足条件的元素生成另一个流//过滤掉字长大于5的词,打印出来Listwords=ImmutableList.of("hello","java8","hello","stream");words.stream().filter(word->word.length()>5).collect(Collectors.toList()).forEach(System.out::println);//输出:stream拷贝代码(2)终端操作终端操作将流转换成具体的返回值,如List、Integer等,常见的终端操作包括:foreach、min、max、count等,foreach很常见,这里举个例子最多//寻找最大值Listintegers=Arrays.asList(6,20,19);integers.stream().max(Integer::compareTo).ifPresent(System.out::println);//输出:20复制代码5.实战:使用Stream重构旧代码如果有需求:过滤掉年龄大于20且分数大于95的同学。使用for循环写法:privateListgetStudents(){Students1=newStudent("xiaoli",18,95);Students2=newStudent("xiaoming",21,100);Students3=newStudent("xiaohua",19,98);ListstudentList=Lists.newArrayList();studentList.add(s1);studentList.add(s2);studentList.add(s3);returnstudentList;}publicvoidrefactorBefore(){ListstudentList=getStudents();//使用临时listListresultList=Lists.newArrayList();for(Students:studentList){if(s.getAge()>20&&s.getScore()>95){resultList.add(s);}}//output:Student{name=xiaoming,age=21,score=100}resultList.forEach(System.out::println);}复制代码并使用for循环初始化一个临时列表来存储最终结果。整体看起来不够优雅简洁。使用lambda和stream重构后:publicvoidrefactorAfter(){ListstudentLists=getStudents();//output:Student{name=xiaoming,age=21,score=100}studentLists.stream().filter(this::filterStudents).forEach(System.out::println);}privatebooleanfilterStudents(Studentstudent){//过滤年龄大于20且成绩大于95的学生返回student.getAge()>20&&student.getScore()>95;}Copy代码使用filter和方法引用,使代码清晰明了,不需要声明临时列表,非常方便。6、使用Stream时常见的误区(一)误区一:流对象的重复消费流对象一旦被消费,就不能重复消费了。Liststrings=Arrays.asList("hello","java8","stream");Streamstream=strings.stream();stream.forEach(System.out::println);//okstream.forEach(System.out::println);//IllegalStateException复制代码上面代码执行后报错:java.lang.IllegalStateException:streamhasalreadybeenoperationedorclosed(2)误区二:修改流操作过程中数据源尝试添加新的字符串对象,结果报错:Liststrings=Arrays.asList("hello","java8","stream");//expect:HELLOJAVA8STREAMWORLD,butthrowUnsupportedOperationExceptionstrings.stream().map(s->{strings.add("world");returns.toUpperCase();}).forEach(System.out::println);复制代码注意:操作流程中不要修改数据源。综上所述,java8流式编程一定程度上可以让代码变得漂亮,但也必须避免常见的陷阱,比如:不要重复消费对象,不要修改数据源。