大家好,感谢邀请,今天来为大家分享一下stream是什么意思的问题,以及和8新特性之stream的一些困惑,大家要是还不太明白的话,也没有关系,因为接下来将为大家分享,希望可以帮助到大家,解决大家的问题,下面就开始吧!
Stream字面意思是流,在java中是指一个来自数据源的元素队列并支持聚合操作,存在于java.util包中,又或者说是能应用在一组元素上一次执行的操作序列。(stream是一个由特定类型对象组成的一个支持聚合操作的队列。)注意Java中的Stream并不会存储元素,而是按需计算。关于这个概念需要以下几点解释:1、数据源流的来源。它可以是列表,集合,数组(java.util.Collection的子类),I/Ochannel,产生器generator等(注意Map是不支持的);2、聚合操作。类似于SQL语句一样的操作,如filter,map,reduce,find,match,sorted等。因此stream流和以前的Collection操作是完全不同,Stream操作还有两个非常基础的特征:Pipelining和内部迭代。
Pipelining也就是中间操作,它都会返回流对象本身。这样多个操作的设计可以串联起不同的运算操作,进而形成一个管道,如同流式风格(fluentstyle)。这样做还可以对操作进行优化,比如延迟执行(laziness)和短路(short-circuiting)等。内部迭代,以前对集合遍历都是通过Iterator或者For-Each的方式来显式的在集合外部进行迭代,这种方式叫做外部迭代。而我们的Stream则提供了内部迭代方式,是通过访问者模式(Visitor)来实现的。
也就是说Stream操作分为中间操作和最终操作两种。其中最终操作用于返回特定类型的计算结果,而中间操作则返回Stream对象本身,这样就可以将多个操作依次串起来且使得操作优化成为可能。
在Java1.8中,集合接口提供了两个方法来生成流:stream()串行流和parallelStream()并行流,即Stream的操作可以分为串行stream()和并行parallelStream()。举个例子来说:
List<String>strings=Arrays.asList("who","what","when","why","which");\nList<String>filterd=strings.stream().filter(string->!string.isEmpty()).collect(Collectors.toList());流的各种运算操作
接下来介绍流的各种操作运算,使得你在适当的时候可以选择相应的流运算。
Stream提供了新的方法forEach来迭代流中的每个数据。举个例子来说:
List<String>stringList=Arrays.asList("who","what","when","why","which");\n\n//方式一:JDK1.8之前的循环方式\nfor(Stringstring:stringList){\nSystem.out.println(string);\n}\n\n//方式二:使用Stream的forEach方法\nstringList.stream().forEach(e->System.out.println(e));\n\n//方式三:方式二的简化形式,因为方法引用也属于函数式接口,因此Lambda表达式可以用方法引用来代替\nstringList.stream().forEach(System.out::println);\n2、filter过滤
filter方法用于通过设置条件来过滤出满足条件的元素。举个例子来说,下面就是用于输出字符串列表中的空字符串的个数:
List<String>stringList=Arrays.asList("","welcome","","to","visit","my","","website");\nlongcount=stringList.stream().filter(e->e.isEmpty()).count();\nSystem.out.println(count);\n3、map映射
请注意这里的map不是指地图map,而是一种函数,用于映射每个元素执行某些操作得到对应的结果。举个例子来说,下面就是使用map来输出元素对应的平方数:
List<Integer>integerList=Arrays.asList(2,3,4,5,6);\nList<Integer>integers=integerList.stream().map(i->i*i).collect(Collectors.toList());\nintegerList.stream().forEach(System.out::println);\n
上面介绍的只是map的最基本用法。map对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。顾名思义像mapToInt就是将原始Stream转换成一个新的Stream,不过新生成的Stream中的元素都是int类型。三个变种方法可以免除自动装箱/拆箱的额外消耗。map方法示意图:
flatMap映射和map映射类似,不过它的每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中,说白了就是将几个小的list合并成一个大的list。flatMap方法示意图:
合并的过程可以参看下面这张图片:
举个例子来说,下面是jdk1.8之前的合并方式,需要先构造一个复合类型List,然后通过两次遍历循环来实现将复合类型List转为单一类型List,这个过程其实挺复杂的:
List<String>fruitList=Arrays.asList("banana","orange","watermelon");\nList<String>vegetableList=Arrays.asList("kale","leek","carrot");\nList<String>transportList=Arrays.asList("car","bike","train");\n\n//将多个元素合成一个复合类型集合,元素类型List<String>\nList<List<String>>lists=newArrayList<>();\nlists.add(fruitList);\nlists.add(vegetableList);\nlists.add(transportList);\n\n//将多个元素合成一个单一类型集合,元素类型String\nList<String>newList=newArrayList<>();\nfor(List<String>list:lists){\nfor(Stringitem:list){\nnewList.add(item);\n}\n}\n
那么使用jdk1.8提供的stream流,同时辅助of、collect和flatMap就可以直接进行转换:
List<String>fruitList=Arrays.asList("banana","orange","watermelon");\nList<String>vegetableList=Arrays.asList("kale","leek","carrot");\nList<String>transportList=Arrays.asList("car","bike","train");\n\n//将多个元素合成一个复合类型集合,元素类型List<String>\nList<List<String>>lists=Stream.of(fruitList,vegetableList,transportList).collect(Collectors.toList());\n\n//将多个元素合成一个单一类型集合,元素类型String\nList<String>flatMap=Stream.of(fruitList,vegetableList,transportList)\n.flatMap(list->list.stream())\n.collect(Collectors.toList());\nSystem.out.println(flatMap);\n5、sorted排序
sorted方法用于对流进行排序。举个例子来说,下面的代码就是用于对字符串按照给定的规则进行排序并输出:
List<String>stringList=Arrays.asList("c","a","f","d","b","e");\nstringList.stream().sorted((s1,s2)->s1.compareTo(s2)).forEach(System.out::println);\n
再举个例子,对10个随机数进行排序并输出:
Randomrandom=newRandom();\nrandom.ints().limit(10).sorted().forEach(System.out::println);\n6、distinct去除重复
distinct方法用于去除流中重复的元素,缺点就是不能设置去重的条件。举个例子来说:
List<String>stringList=Arrays.asList("do","what","you","want","to","do","and","do","it");\nstringList.stream().distinct().forEach(System.out::println);\n7、of生成Stream对象
of方法用于生成Stream对象,注意它是Stream对象的方法。举个例子来说:
Stream<Object>objectStream=Stream.of("do","what","you","want","to","do","and","do","it");\nobjectStream.forEach(System.out::println);\n8、count计算总数
count方法用于计算流中元素的总数。举个例子来说:
Stream<Object>objectStream=Stream.of("do","what","you","want","to","do","and","do","it");\nlongcount=objectStream.count();\nSystem.out.println(count);\n9、min和max最小/最大
min/max方法用于返回流中那个元素最小(最大)的,注意返回的是一个Optional对象。举个例子来说:
List<String>integerList=Arrays.asList("1","2","3","4","5","6","7");\nOptional<String>optionalInteger=integerList.stream().max((a,b)->a.compareTo(b));\nStringresult=optionalInteger.get();\nSystem.out.println(result);//结果为7\n10、collect
collect方法的使用较为复杂,这里仅仅介绍一些常用的方法即可。collect方法可以将Stream转为Collection对象或者是Object类型的数组等,举个例子来说:
List<String>stringList=Arrays.asList("do","what","you","want","to","do","and","do","it");\n//Stream转Collection\nstringList.stream().collect(Collectors.toList());\n//Stream转Object[]数组\nObject[]objects=stringList.stream().toArray();\n11、concat
concat方法用于合并流对象,注意这时Stream对象的方法。举个例子来说:
List<String>fruitList=Arrays.asList("banana","orange","watermelon");\nList<String>vegetableList=Arrays.asList("kale","leek","carrot");\n\nStream<String>stringStream=Stream.concat(fruitList.stream(),vegetableList.stream());\nstringStream.forEach(System.out::println);\n12、skip和limit
通常大家都会将skip和limit放在一块进行学习和对比,那是因为两者具有类似的作用,都是对流进行裁剪的中间方法。
skip方法。先来看skip方法,顾名思义skip(n)用于跳过前面n个元素,然后再返回新的流,如图所示:
为了验证上面图片的作用,这里举一个例子来进行说明:
publicstaticvoidskipTest(longn){\nStream<Integer>integerStream=Stream.of(1,2,3,4,5,6);\nintegerStream.skip(n).forEach(System.out::println);\n}\n
方法skip()中的参数n不同将会导致不同的结果,具体情况如下:(1)、当n<0时,运行结果会抛出IllegalArgumentException异常;(2)、当n=0时,相当没有跳过任何元素,原封不动地截取流中的元素(这种通常没有意义,基本不会这样操作);(3)、当0<n<length时,表示跳过n个元素后(不包括元素n),结果返回含有剩下的元素的流(使用频率较多);(4)、当n>=length时,表示跳过所有元素,结果返回空流,你可以使用count方法来判断此时流中元素的总数必定为0。
limit方法。说完了skip()方法,接下来聊聊limit()方法。顾名思义这个就是限制流中的元素,即用于将前n个元素返回新的流,如图所示:
同样也通过举一个例子来进行说明:
publicstaticvoidlimitTest(longn){\nStream<Integer>integerStream=Stream.of(1,2,3,4,5,6);\nintegerStream.limit(n).forEach(System.out::println);\n}\n
方法limit()中的参数n不同将会导致不同的结果,具体情况如下:(1)、当n<0时,运行结果会抛出IllegalArgumentException异常;(2)、当n=0时,相当不取元素,结果返回空流;(3)、当0<n<length时,表示取前n个元素,结果返回新的流(使用频率较多);(4)、当n>=length时,表示取所有元素,结果返回流本身,你可以使用count方法来判断此时流中元素的总数必定为length。
区别:注意这里谈skip和limit方法的区别是局限于有限流。skip和limit方法都是对流进行截取操作,区别在于skip方法必须时刻监测流中元素的状态,才能判断是否需要丢弃,因此skip属于状态操作。而limit只关心截取的是否是其length,是就立马中断操作返回流,因此limit属于中断操作。
parallelStream是流并行处理程序的代替方法。举个例子来说,下面是使用parallelStream并行流来输出空字符串的数量:
List<String>stringList=Arrays.asList("a","","b","","e","","c","","f");\n//获取空字符串的数量\nlongcount=stringList.parallelStream().filter(string->string.isEmpty()).count();\nSystem.out.println(count);//4\n14、anyMatch、allMatch和noneMatch
anyMatch方法用于判断流中是否存在满足特定条件的元素,返回类型是boolean类型。(只要有一个条件满足即返回true)
List<String>stringList=Arrays.asList("hello","the","fruit","name","is","banana");\nBooleanresult=stringList.parallelStream().anyMatch(item->item.equals("name"));\nSystem.out.println(result);//true\n
allMatch方法用于判断流中是否存在满足特定条件的元素,返回类型是boolean类型。(必须全部满足才会返回true)
List<String>stringList=Arrays.asList("hello","the","fruit","name","is","banana");\nBooleanresult=stringList.parallelStream().allMatch(item->item.equals("name"));\nSystem.out.println(result);//false\n
noneMatch方法用于判断流中是否存在满足特定条件的元素,返回类型是boolean类型。(全都不满足才会返回true)
List<String>stringList=Arrays.asList("hello","the","fruit","name","is","banana");\nBooleanresult=stringList.parallelStream().noneMatch(item->item.equals("name"));\nSystem.out.println(result);//false\n
上面这个例子就是因为有一个满足条件就返回了false。
reduce的意思是减少,而Stream中reduce方法就是用于实现这个目的,它根据一定的规则将Stream中的元素进行计算后返回一个唯一的值。举个例子来说:
Stream<String>stringStream=Stream.of("my","name","is","envy");\nOptional<String>stringOptional=stringStream.reduce((before,after)->before+"、"+after);\nstringOptional.ifPresent(System.out::println);//my、name、is、envy\n16、findFirst和findAny
findFirst方法用于返回list列表中第一个元素,注意如果元素不存在则抛异常。举个例子来说:
List<String>stringList=Arrays.asList("do","what","you","want","to","do","and","do","it");\nOptional<String>result=stringList.parallelStream().findFirst();\nSystem.out.println(result.get());//do\n
注意若Optional为空,则get方法会抛出异常,但是你可以使用orElse(defaultVal);或使用orElseGet(()->{//doSomething;returndefaultVal;});来返回默认值。举个例子来说:
List<String>stringList=Arrays.asList();\nOptional<String>result=stringList.parallelStream().findFirst();\nSystem.out.println(result.orElse("没有元素"));//没有元素\n\nList<String>stringList=Arrays.asList();\nOptional<String>result=stringList.parallelStream().findFirst();\nSystem.out.println(result.orElseGet(()->{return"没有元素";}));//没有元素\n17、summaryStatistics统计
summaryStatistics方法用于产生统计结果的收集器,举个例子来说:
List<Integer>integerList=Arrays.asList(3,2,3,5,6,8,9);\nIntSummaryStatisticsresult=integerList.stream().mapToInt((x)->x).summaryStatistics();\nSystem.out.println("列表中最大的数:"+result.getMax());\nSystem.out.println("列表中最小的数:"+result.getMin());\nSystem.out.println("列表中所有数之和:"+result.getSum());\nSystem.out.println("列表中所有数的平均数:"+result.getAverage());\nSystem.out.println("列表中元素的个数:"+result.getCount());\n18、Joining集合元素的拼接
集合元素的拼接,其实就是指定分隔符将列表中的元素合并成一个字符串,注意joining方法是存在于Collectors中的。举个例子来说:
List<String>stringList=Arrays.asList("my","name","is");\nSystem.out.println(stringList);//[my,name,is]\n\nStringresult=stringList.stream().collect(Collectors.joining(","));\nSystem.out.println(result);//my,name,is\n\nStringnewString=Stream.of("I","come","frombei").collect(\nCollectors.collectingAndThen(\nCollectors.joining(","),x->x+"jing"));\nSystem.out.println(newString);//I,come,frombeijing\n19、Collectors之流转换成集合
Collectors类实现了很多归约操作,例如将流转换成集合和聚合元素等,Collectors可用于返回列表或字符串。下面通过举一些经常会使用到的例子来进行说明:先在外部新建一个Student实体类,后续会使用到:
publicclassStudent{\nprivateStringname;\nprivateLongscore;\n//getter/setter/toString/AllArgsConstructor\n}\n
然后看下面的例子代码:
List<Integer>integerList=Arrays.asList(1,2,3,4,5);\n//流转列表\nList<Integer>newList=integerList.stream().map(i->i*10).collect(Collectors.toList());\nSystem.out.println("新列表:"+newList);//[10,20,30,40,50]\n\n//流转集合\nSet<Integer>integerSet=integerList.stream().map(i->i*10).collect(Collectors.toSet());\nSystem.out.println("新集合:"+integerSet);//[50,20,40,10,30]\n\n//流转映射\nMap<String,String>stringStringMap=integerList.stream().map(i->i*10).collect(\nCollectors.toMap(key->"key"+key/10,value->"value:"+value)\n);\nSystem.out.println("新映射:"+stringStringMap);//{key1=value:10,key2=value:20,key5=value:50,key3=value:30,key4=value:40}\n\n\n//流转有序集合TreeSet\nTreeSet<Integer>integerTreeSet=Stream.of(1,6,3,7,2).collect(Collectors.toCollection(TreeSet::new));\nSystem.out.println("新有序集合:"+integerTreeSet);//[1,2,3,6,7]\n\n\n//自定义对象流\nList<Student>studentList=Arrays.asList(\nnewStudent("envy",100L),\nnewStudent("movie",90L),\nnewStudent("book",80L)\n);\n\n//获得对象\nMap<String,Student>studentAndModelMap=studentList.stream().collect(Collectors.toMap(\nStudent::getName,Function.identity()\n));\nStudentstudent=studentAndModelMap.get("envy");\nSystem.out.println(student);//Student{name='envy',score=100}\n\n//获得属性\nMap<String,Long>studentAndStudentScoreMap=studentList.stream().collect(Collectors.toMap(\nStudent::getName,Student::getScore\n));\nLongscore=studentAndStudentScoreMap.get("envy");\nSystem.out.println(score);//100\n20、Collectors之元素聚合
其实这个元素聚合归根结底还是Collectors类中的方,下面就来介绍聚合元素这个操作,Collectors可用于返回列表或字符串。下面通过举一些经常会使用到的例子来进行说明:
//元素聚合\n\nList<Integer>integerList=Arrays.asList(1,5,8,3,6,2,9,7,4);\n\n//求最大值\nIntegermaxValue=integerList.stream().collect(\nCollectors.collectingAndThen(\nCollectors.maxBy((a,b)->a-b),Optional::get\n));\nSystem.out.println(maxValue);//9\n\n//求最小值\nIntegerminValue=integerList.stream().collect(\nCollectors.collectingAndThen(\nCollectors.minBy((a,b)->a-b),Optional::get\n));\nSystem.out.println(minValue);//1\n\n//求和\nIntegersumValue=integerList.stream().collect(\nCollectors.summingInt(item->item)\n);\nSystem.out.println(sumValue);//45\n\n//平均值\nDoubleavgValue=integerList.stream().collect(\nCollectors.averagingDouble(x->x)\n);\nSystem.out.println(avgValue);//5.0\n\n\n//集合转映射\nStringlistToMap=Stream.of("my","name","is","envy").collect(\nCollectors.mapping(\nx->x.toUpperCase(),Collectors.joining(",")\n)\n);\nSystem.out.println(listToMap);//MY,NAME,IS,ENVY\n21、累计操作
reducing累计操作,也是Collectors类中的方法,用于进行元素的累计操作。先来看一个例子,用于计算出[2,3,5,6]这个列表中所有元素各加1之后的所有元素之和,很简单口算都可以知道答案是20。你可能有很多种想法,这里提供几种以供你参考:
//方法一,不使用stream\nint[]ints={2,3,5,6};\nintresultSum=0;\nfor(inti:ints){\ni++;\nresultSum+=i;\n}\nSystem.out.println(resultSum);//20\n\n//方法二,使用stream流的map+summingInt方法\nList<Integer>integerList=Arrays.asList(2,3,5,6);\nIntegerintegerOne=integerList.stream().map(i->i+1).collect(\nCollectors.summingInt(x->x)\n);\nSystem.out.println(integerOne);//20\n\n\n//方法三,使用stream流的reducing方法\nIntegerintegerTwo=integerList.stream().collect(\nCollectors.reducing(0,x->x+1,(sum,b)->{\nreturnsum+b;\n})\n);\nSystem.out.println(integerTwo);//20\n\n\n//reducing还可以用于更复杂的累计计算,不局限于加减乘除等操作\nIntegerintegerThree=integerList.stream().collect(\nCollectors.reducing(1,x->x+1,(result,b)->{\nreturnresult*b;\n})\n);\nSystem.out.println(integerThree);//3*4*6*7=504\n22、groupingBy分组
groupingBy分组这个功能在实际开发中用的非常多,因此有必须要好好用一下,它也是存在于Collectors类中的。来看一下这个Collectors.groupingBy方法的源码,它有三个重载方法,这里就以只有一个参数的方法为例进行说明:
publicstatic<T,K>Collector<T,?,Map<K,List<T>>>\ngroupingBy(Function<?superT,?extendsK>classifier){\nreturngroupingBy(classifier,toList());\n}\n
可以发现它的参数只有一个:Function<?superT,?extendsK>classifier,类型是Function类型也就是个函数,Function的返回值可以是要分组的条件,或者是要分组的字段。groupingBy方法的返回的结果是一个Map,其中key的数据类型为Function体中的计算类型(也就是参数类型),value是List<T>类型也就是分组的结果。
接下来通过一个例子来介绍如何使用它,这个例子也非常简单,给定[1,2,3,4,5,6,7,8,9]这个列表,如何将其按照奇数和偶数来划分为两组。用以往的知识你可能会这样操作:
List<Integer>oneList=newArrayList<>();//奇数列表\nList<Integer>twoList=newArrayList<>();//偶数列表\nfor(Integeritem:integerList){\nif(item%2==0){\ntwoList.add(item);\n}else{\noneList.add(item);\n}\n}\nSystem.out.println(oneList);//[1,3,5,7,9]\nSystem.out.println(twoList);//[2,4,6,8]\n
但是如果你使用了stream那就变得简单多了:
//方法二,使用stream\nMap<Boolean,List<Integer>>resultMap=integerList.stream().collect(\nCollectors.groupingBy(item->item%2==0)\n);\nSystem.out.println(resultMap);//{false=[1,3,5,7,9],true=[2,4,6,8]}\n\nMap<Boolean,List<Integer>>twoPartition=integerList.stream().collect(\nCollectors.partitioningBy(item->item%2==0)\n);\nSystem.out.println(twoPartition);//twoPartition就是将结果为为两组\n\n\n//还可以自定义分组的条件\nList<Student>studentList=Arrays.asList(\nnewStudent("book",100L,1),\nnewStudent("movie",90L,2),\nnewStudent("fruit",80L,2),\nnewStudent("vegetable",70L,4)\n);\n//根据某个字段进行分组\nMap<Integer,List<Student>>studentMap=studentList.stream().collect(\nCollectors.groupingBy(item->item.getId())\n);\nSystem.out.println(studentMap);\n//{1=[Student{name='book',score=100}],2=[Student{name='movie',score=90},Student{name='fruit',score=80}],4=[Student{name='vegetable',score=70}]}\n\n//还可以结合前面的统计结果处理器来对结果进行分析\nMap<Integer,LongSummaryStatistics>summaryStatisticsMap=studentList.stream().collect(\nCollectors.groupingBy(\nStudent::getId,Collectors.summarizingLong(Student::getScore)\n)\n);\n\nLongSummaryStatisticsstatisticsOne=summaryStatisticsMap.get(1);\nLongSummaryStatisticsstatisticsTwo=summaryStatisticsMap.get(2);\n\nSystem.out.println(statisticsOne.getMax());//100\nSystem.out.println(statisticsOne.getMin());//100\nSystem.out.println(statisticsOne.getAverage());//100.0\nSystem.out.println(statisticsOne.getCount());//1\nSystem.out.println(statisticsOne.getSum());//100.0\n\nSystem.out.println("*********");\nSystem.out.println(statisticsTwo.getMax());//90\nSystem.out.println(statisticsTwo.getMin());//80\nSystem.out.println(statisticsTwo.getAverage());//85.0\nSystem.out.println(statisticsTwo.getCount());//2\nSystem.out.println(statisticsTwo.getSum());//170\n}\n
上面基本上把日常开发过程中可能会遇到的场景都进行了介绍,但是我觉得这是做了第一步如何使用它,后续会出一些文章来好好研究里面的源码,同时会对上面的一些方法进行更深层次的研究和使用。
好了,关于stream是什么意思和8新特性之stream的问题到这里结束啦,希望可以解决您的问题哈!