一.流简介
首先,Stream流有一些特性:Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
Stream不保存数据,故每个Stream流只能使用一次。
关于应用在Stream流上的操作,可以分成两种:Intermediate(中间操作)和Terminal(终止操作)。中间操作的返回结果都是Stream,故可以多个中间操作叠加;终止操作用于返回我们最终需要的数据,只能有一个终止操作。至于哪些方法是中间操作,哪些方法是终止操作,我们一会儿再说。
使用Stream流,可以清楚地知道我们要对一个数据集做何种操作,可读性强。而且可以很轻松地获取并行化Stream流,不用自己编写多线程代码,可以让我们更加专注于业务逻辑。
接下来看一下Stream流的接口继承关系:
基础接口BaseStream<T, S extends BaseStream<T, S>>包含如下方法:
Iterator<T> iterator();
Spliterator<T> spliterator();
boolean isParallel(); //判断是否是并行化流
S sequential(); //将流串行化
S parallel(); //将流并行化
S unordered(); //解除有序流的顺序限制,发挥并行处理的性能优势
S onClose(Runnable closeHandler);
void close();
默认情况下,从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。unordered()方法可以解除有序流的顺序限制,更好地发挥并行处理的性能优势,例如distinct将保存任意一个唯一元素而不是第一个,limit将保留任意n个元素而不是前n个。
BaseStream的四个子接口方法都差不多,只是IntStream、LongStream、DoubleStream直接存储基本类型,可以避免自动装/拆箱,效率会更高一些。下面以Stream为例,将接口的方法分类讲解一下。
二.流操作
java.util.stream.Stream有许多操作,但是可以分为两类:一种为中间操作,一种为终端操作。
中间操作就是操作后返回一个流,比如map,filter,limit等等,中间操作只有在终端操作时才执行。
终端操作就是把流变为结果不是流的值,比如void,List,Integer等等。比如forEach,count,collect等等。
使用流一般包含三个动作:生成流,中间操作,终端操作。
1、 流的生成方法
Collection接口的stream()或parallelStream()方法
静态的Stream.of()、Stream.empty()方法
Arrays.stream(array, from, to)
静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数
静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数
Pattern接口的splitAsStream(input)方法
静态的Files.lines(path)、Files.lines(path, charSet)方法
静态的Stream.concat()方法将两个流连接起来
……
注意,无限流的存在,侧面说明了流是惰性的,即每当用到一个元素时,才会在这个元素上执行这一系列操作。
2、 流的Intermediate方法(中间操作)
filter(Predicate)
将结果为false的元素过滤掉
map(fun)
转换元素的值,可以用方法引元或者lambda表达式
flatMap(fun)
若元素是流,将流摊平为正常元素,再进行元素转换
limit(n)
保留前n个元素
skip(n)
跳过前n个元素
distinct()
剔除重复元素
sorted()
将Comparable元素的流排序
sorted(Comparator)
将流元素按Comparator排序
peek(fun)
流不变,但会把每个元素传入fun执行,可以用作调试
3、 流的Terminal方法(终结操作)
约简操作
max(Comparator)
min(Comparator)
count()
findFirst()
返回第一个元素
findAny()
返回任意元素
anyMatch(Predicate)
任意元素匹配时返回true
allMatch(Predicate)
所有元素匹配时返回true
noneMatch(Predicate)
没有元素匹配时返回true
reduce(fun)
从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数
reduce(a, fun)
a为幺元值,作为累积器的起点
reduce(a, fun1, fun2)
与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并
收集操作
iterator()
forEach(fun)
forEachOrdered(fun)
可以应用在并行流上以保持元素顺序
toArray()
toArray(T[] :: new)
返回正确的元素类型
collect(Collector)
collect(fun1, fun2, fun3)
fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来
然后再看一下有哪些Collector收集器:
Collectors.toList()
Collectors.toSet()
Collectors.toCollection(集合的构造器引用)
Collectors.joining()、Collectors.joining(delimiter)、Collectors.joining(delimiter、prefix、suffix)
字符串元素连接
Collectors.summarizingInt/Long/Double(ToInt/Long/DoubleFunction)
产生Int/Long/DoubleSummaryStatistics对象,它有getCount、getSum、getMax、getMin方法,注意在没有元素时,getMax和getMin返回Integer/Long/Double.MAX/MIN_VALUE
Collectors.toMap(fun1, fun2)/toConcurrentMap
两个fun用来产生键和值,若值为元素本身,则fun2为Function.identity()
Collectors.toMap(fun1, fun2, fun3)/toConcurrentMap
fun3用于解决键冲突,例如(oldValue, newValue) -> oldValue,有冲突时保留原值
Collectors.toMap(fun1, fun2, fun3, fun4)/toConcurrentMap
默认返回HashMap或ConcurrentHashMap,fun4可以指定返回的Map类型,为对应的构造器引元
Collectors.groupingBy(fun)/groupingByConcurrent(fun)
fun是分类函数,生成Map,键是fun函数结果,值是具有相同fun函数结果元素的列表
Collectors.partitioningBy(fun)
键是true/false,当fun是断言函数时用此方法,比groupingBy(fun)更高效
Collectors.groupingBy(fun1, fun2)
fun2为下游收集器,可以将列表转换成其他形式,例如toSet()、counting()、summingInt/Long/Double(fun)、maxBy(Comparator)、minBy(Comparator)、mapping(fun1, fun2)(fun1为转换函数,fun2为下游收集器)
最后提一下基本类型流,与对象流的不同点如下:
IntStream和LongStream有range(start, end)和rangeClosed(start, end)方法,可以生成步长为1的整数范围,前者不包括end,后者包括end
toArray方法将返回基本类型数组
具有sum、average、max、min方法
summaryStatics()方法会产生类型为Int/Long/DoubleSummaryStatistics的对象
可以使用Random类的ints、longs、doubles方法产生随机数构成的流
对象流转换为基本类型流:mapToInt()、mapToLong()、mapToDouble()
基本类型流转换为对象流:boxed()
以上就是对Java8的Stream流的介绍,日后在实践中有新的体会之后还会再来补充……
三.使用流
1构建流
由值生成流:
IntStream i = IntStream.of(1,2,3,4); Stream<String> s = Stream.of("a","b","c"); Stream<String> emptyStream = Stream.empty();<!--创建一个空的流-->
由数组创建流:
int[] numbers = {1,2,3,4}; IntStream intStream = Arrays.stream(numbers);
由文件生成流:
java.nio.file.Files很多方法都会返回流。比如Files.lines会返回指定文件各行构成的字符串流
// hello // world // stream >>>data.txt数据 try(Stream<String> lines = Files.lines(Paths.get("D:\\Desktop\\data.txt"))){ long words =lines.flatMap(line->Arrays.stream(line.split(""))).distinct().count(); System.out.println(words); }catch(IOException e){ e.printStackTrace(); }
由函数生成流:
Stream Api有两个静态方法生成流:Stream.iterate,Stream.generate。这两个方法可以生成无限流,可以无限制的计算产生流数据。一定要加limit限制大小。
Stream.iterate(0, x->x+1).limit(10).forEach(System.out::println); Stream.generate(()->2).limit(10).forEach(System.out::println);
一般使用的是集合生成的流,Collection接口有默认方法产生流。只要实现Collection的类都可以用这个方法。
List<Integer> list = Arrays.asList(1,2,3,4); list.stream().forEach(System.out::println);
2筛选
流的filter方法,参数为Predicate。返回满足Predicate的元素。也就是返回true的元素。
List<Integer> list = Arrays.asList(1,2,3,4); list.stream().filter(i->i>=2).forEach(System.out::println);
流的distinct方法返回各异的元素,返回的元素不能重复。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); list.stream().distinct().forEach(System.out::println);
流的limit方法限制最多返回多少元素。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); list.stream().limit(2).forEach(System.out::println);
流的skip方法不要前多n个元素,n大于满足条件的元素个数就返回空的流。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); list.stream().filter(i->i>=2).skip(2).forEach(System.out::println);
3映射
流的map方法作用于每个元素,并且返回一个新的元素,参数为Function。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); list.stream().filter(i->i>=2).map(a->"map"+a).forEach(System.out::println);
map可以改变Stream的类型,上面就是把Stream<Integer>变为Stream<String>了。
流的flatMap方法可以把各个数组映射的流合为一个流。例如得到文件字母的个数。
lines.flatMap(line->Arrays.stream(line.split(""))).distinct().count();
如果直接用map(line->line.spilt(""))得到的是很多个Stream<String[]>。
如果用map(line->Arrays.stream(line.split("")))得到的是很多个Stream<String>。
flatMap就是把很多个Stream<String>合为一个Stream<String>这样就可以算字母个数了。不然分为很多个流不能算字母个数。
4查找与匹配
anyMatch方法参数为Predicate。返回一个boolean值,表示流中是否有元素满足Predicate。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); boolean result = list.stream().anyMatch(i->i==2);
allMatch方法与anyMatch差不多,表示所有的元素都满足才返回true。noneMatch方法表示没有元素满足。
这三个操作都是短路操作。短路操作就是不需要遍历所有的元素就能得到结果。就可&&和||操作符一样。limit方法就是一个短路操作。
findAny方法,没有参数,表示返回随机的一个元素。返回一个Optional,因为有可能元素不存在。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); Optional<Integer> result = list.stream().findAny(); System.out.println(result.orElse(0));
findFirst方法,没有参数,返回第一个元素。返回Optional。
5归约
流的reduce操作就是将流中的元素结合得到一个值。根据reduce方法中的操作,两两结合得到一个值,然后跟下一个元素结合,就这样一直到最后一个元素,得到最终的值。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); Optional<Integer> sum = list.stream().reduce(Integer::sum);<!--reduce可以指定初始值--> Optional<Integer> max = list.stream().reduce(Integer::max); Optional<Integer> min = list.stream().reduce(Integer::min);
流还有max和min方法,参数为Comparator。
List<Integer> list = Arrays.asList(1,2,3,4,2,3); Optional<Integer> max = list.stream().max(comparing(Integer::intValue)); Optional<Integer> min = list.stream().min(comparing(Integer::intValue));
6数值流
上面讲的操作如Stream<Integer>会有装箱拆箱的操作。Stream中有IntStream、 DoubleStream和
LongStream分别表示流中的元素为int,double,long,这样可以避免装箱拆箱。
mapToInt、 mapToDouble和mapToLong着三个方法可以将Stream<T>转为数值流。数值流中有max,min,sum,average等方法。
List<A> list = Arrays.asList(new A(1),new A(5),new A(3)); int sum = list.stream().mapToInt(a->a.getNum()).sum(); OptionalInt max = list.stream().mapToInt(a->a.getNum()).max(); OptionalInt min = list.stream().mapToInt(a->a.getNum()).min(); OptionalDouble ave = list.stream().mapToInt(a->a.getNum()).average();
boxed() 方法可以将数值流转为对象流。
List<A> list = Arrays.asList(new A(1),new A(5),new A(3)); Stream<Integer> i = list.stream().mapToInt(a->a.getNum()).boxed();
IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
IntStream.range(1, 100).filter(i->i%2==0).count();
(文章引用CSDN作者:逗比123号、Francis长风)
本文暂时没有评论,来添加一个吧(●'◡'●)