Java Stream 使用详解Word格式.docx
《Java Stream 使用详解Word格式.docx》由会员分享,可在线阅读,更多相关《Java Stream 使用详解Word格式.docx(8页珍藏版)》请在冰豆网上搜索。
通过parallel()方法可以将串行流转换成并行流,sequential()方法将流转换成并行流。
除非方法的Javadoc中指明了方法在并行执行的时候结果是不确定(比如findAny、forEach),否则串行和并行执行的结果应该是一样的。
不干涉Non-interference
流可以从非线程安全的集合中创建,当流的管道执行的时候,非concurrent数据源不应该被改变。
下面的代码会抛出java.util.ConcurrentModificationException异常:
List<
String>
l=newArrayList(Arrays.asList("
one"
"
two"
));
Stream<
sl=l.stream();
sl.forEach(s->
l.add("
three"
在设置中间操作的时候,可以更改数据源,只有在执行终点操作的时候,才有可能出现并发问题(抛出异常,或者不期望的结果),比如下面的代码不会抛出异常:
l.add("
);
sl.forEach(System.out:
:
println);
对于concurrent数据源,不会有这样的问题,比如下面的代码很正常:
l=newCopyOnWriteArrayList<
>
(Arrays.asList("
虽然我们上面例子是在终点操作中对非并发数据源进行修改,但是非并发数据源也可能在其它线程中修改,同样会有并发问题。
无状态Statelessbehaviors
大部分流的操作的参数都是函数式接口,可以使用Lambda表达式实现。
它们用来描述用户的行为,称之为行为参数(behavioralparameters)。
如果这些行为参数有状态,则流的操作的结果可能是不确定的,比如下面的代码:
……));
classState{booleans;
}finalStatestate=newState();
sl=l.stream().map(e->
{if(state.s)return"
OK"
;
else{state.s=true;
returne;
}});
上面的代码在并行执行时多次的执行结果可能是不同的。
这是因为这个lambda表达式是有状态的。
副作用Side-effects
有副作用的行为参数是被鼓励使用的。
副作用指的是行为参数在执行的时候有输入输入,比如网络输入输出等。
这是因为Java不保证这些副作用对其它线程可见,也不保证相同流管道上的同样的元素的不同的操作运行在同一个线程中。
很多有副作用的行为参数可以被转换成无副作用的实现。
一般来说println()这样的副作用代码不会有害。
ArrayList<
results=newArrayList<
();
stream.filter(s->
pattern.matcher(s).matches()).forEach(s->
results.add(s));
//副作用代码
上面的代码可以改成无副作用的。
results=stream.filter(s->
pattern.matcher(s).matches()).collect(Collectors.toList());
//Noside-effects!
排序Ordering
某些流的返回的元素是有确定顺序的,我们称之为encounterorder。
这个顺序是流提供它的元素的顺序,比如数组的encounterorder是它的元素的排序顺序,List是它的迭代顺序(iterationorder),对于HashSet,它本身就没有encounterorder。
一个流是否是encounterorder主要依赖数据源和它的中间操作,比如数据源List和Array上创建的流是有序的(ordered),但是在HashSet创建的流不是有序的。
sorted()方法可以将流转换成有序的,unordered可以将流转换成无序的。
除此之外,一个操作可能会影响流的有序,比如map方法,它会用不同的值甚至类型替换流中的元素,所以输入元素的有序性已经变得没有意义了,但是对于filter方法来说,它只是丢弃掉一些值而已,输入元素的有序性还是保障的。
对于串行流,流有序与否不会影响其性能,只是会影响确定性(determinism),无序流在多次执行的时候结果可能是不一样的。
对于并行流,去掉有序这个约束可能会提供性能,比如distinct、groupingBy这些聚合操作。
结合性Associativity
一个操作或者函数op满足结合性意味着它满足下面的条件:
(aopb)opc==aop(bopc)
对于并发流来说,如果操作满足结合性,我们就可以并行计算:
aopbopcopd==(aopb)op(copd)
比如min、max以及字符串连接都是满足结合性的。
创建Stream
可以通过多种方式创建流:
1、通过集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()。
2、通过Arrays.stream(Object[])方法,比如Arrays.stream(newint[]{1,2,3})。
3、使用流的静态方法,比如Stream.of(Object[]),IntStream.range(int,int)或者Stream.iterate(Object,UnaryOperator),如Stream.iterate(0,n->
n*2),或者generate(Supplier<
T>
s)如Stream.generate(Math:
random)。
4、BufferedReader.lines()从文件中获得行的流。
5、Files类的操作路径的方法,如list、find、walk等。
6、随机数流Random.ints()。
7、其它一些类提供了创建流的方法,如BitSet.stream(),Pattern.splitAsStream(java.lang.CharSequence),和JarFile.stream()。
8、更底层的使用StreamSupport,它提供了将Spliterator转换成流的方法。
中间操作intermediateoperations
中间操作会返回一个新的流,并且操作是延迟执行的(lazy),它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。
这个Scala集合的转换操作不同,Scala集合转换操作会生成一个新的中间集合,显而易见Java的这种设计会减少中间对象的生成。
下面介绍流的这些中间操作:
distinct
distinct保证输出的流中包含唯一的元素,它是通过Object.equals(Object)来检查是否包含相同的元素。
l=Stream.of("
a"
"
b"
c"
).distinct().collect(Collectors.toList());
System.out.println(l);
//[a,b,c]
filter
filter返回的流中只包含满足断言(predicate)的数据。
下面的代码返回流中的偶数集合。
Integer>
l=IntStream.range(1,10).filter(i->
i%2==0).boxed().collect(Collectors.toList());
//[2,4,6,8]
map
map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。
下面的代码中将字符元素映射成它的哈希码(ASCII值)。
l=Stream.of('
a'
'
b'
c'
).map(c->
c.hashCode()).collect(Collectors.toList());
//[97,98,99]
flatmap
flatmap方法混合了map+flattern的功能,它将映射后的流的元素全部放入到一个新的流中。
它的方法定义如下:
<
R>
Stream<
flatMap(Function<
?
superT,?
extendsStream<
extendsR>
mapper)
可以看到mapper函数会将每一个元素转换成一个流对象,而flatMap方法返回的流包含的元素为mapper生成的流中的元素。
下面这个例子中将一首唐诗生成一个按行分割的流,然后在这个流上调用flatmap得到单词的小写形式的集合,去掉重复的单词然后打印出来。
Stringpoetry="
Where,beforeme,aretheagesthathavegone?
/n"
+"
Andwhere,behindme,arethecominggenerations?
Ithinkofheavenandearth,withoutlimit,withoutend,/n"
AndIamallaloneandmytearsfalldown."
lines=Arrays.stream(poetry.split("
words=lines.flatMap(line->
Arrays.stream(line.split("
"
)));
l=words.map(w->
{if(w.endsWith("
)||w.endsWith("
."
"
))returnw.substring(0,w.length()-1).trim().toLowerCase();
elsereturnw.trim().toLowerCase();
}).distinct().sorted().collect(Collectors.toList());
//[ages,all,alone,am,and,are,before,behind,coming,down,earth,end,fall,generations,gone,have,heaven,i,limit,me,my,of,tears,that,the,think,where,without]
flatMapToDouble、flatMapToInt、flatMapToLong提供了转换成特定流的方法。
limit
limit方法指定数量的元素的流。
对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。
l=IntStream.range(1,100).limit(5).boxed().collect(Collectors.toList());
//[1,2,3,4,5]
peek
peek方法方法会使用一个Consumer消费流中的元素,但是返回的流还是包含原来的流中的元素。
String[]arr=newString[]{"
d"
};
Arrays.stream(arr).peek(System.out:
println)//a,b,c,d.count();
sorted
sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。
sorted(Comparator<
superT>
comparator)可以指定排序的方式。
对于有序流,排序是稳定的。
对于非有序流,不保证排序稳定。
b_123"
c+342"
b#632"
d_123"
l=Arrays.stream(arr).sorted((s1,s2)->
{if(s1.charAt(0)==s2.charAt(0))returns1.substring
(2).compareTo(s2.substring
(2));
elsereturns1.charAt(0)-s2.charAt(0);
}).collect(Collectors.toList());
//[b_123,b#632,c+342,d_123]
skip
skip返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流。
终点操作terminaloperations
Match
publicbooleanallMatch(Predicate<
predicate)publicbooleananyMatch(Predicate<
predicate)publicbooleannoneMatch(Predicate<
predicate)
这一组方法用来检查流中的元素是否满足断言。
allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true
anyMatch只有在任意一个元素满足断言时就返回true,否则flase,
noneMatch只有在所有的元素都不满足断言时才返回true,否则flase,
System.out.println(Stream.of(1,2,3,4,5).allMatch(i->
i>
0));
//trueSystem.out.println(Stream.of(1,2,3,4,5).anyMatch(i->
//trueSystem.out.println(Stream.of(1,2,3,4,5).noneMatch(i->
//falseSout.println(Stream.<
empty().allMatch(i->
//trueSystem.out.println(Stream.<
empty().anyMatch(i->
//falseSystem.out.println(Stream.<
empty().noneMatch(i->
//true
count
count方法返回流中的元素的数量。
它实现为:
mapToLong(e->
1L).sum();
collect
R,A>
Rcollect(Collector<
superT,A,R>
collector)<
Rcollect(Supplier<
supplier,BiConsumer<
R,?
accumulator,BiConsumer<
R,R>
combiner)
使用一个collector执行mutablereduction操作。
辅助类
Collectors
提供了很多的collector,可以满足我们日常的需求,你也可以创建新的collector实现特定的需求。
它是一个值得关注的类,你需要熟悉这些特定的收集器,如聚合类averagingInt、最大最小值maxByminBy、计数counting、分组groupingBy、字符串连接joining、分区partitioningBy、汇总summarizingInt、化简reducing、转换toXXX等。
第二个提供了更底层的功能,它的逻辑类似下面的伪代码:
Rresult=supplier.get();
for(Telement:
thisstream)accumulator.accept(result,element);
returnresult;
例子:
asList=stringStream.collect(ArrayList:
new,ArrayList:
add,ArrayList:
addAll);
Stringconcat=stringStream.collect(StringBuilder:
new,StringBuilder:
append,StringBuilder:
append).toString();
find
findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
findFirst()返回第一个元素,如果流为空,返回空的Optional。
forEach、forEachOrdered
forEach遍历流的每一个元素,执行指定的action。
它是一个终点操作,和peek方法不同。
这个方法不担保按照流的encounterorder顺序执行,如果对于有序流按照它的encounterorder顺序执行,你可以使用forEachOrdered方法。
Stream.of(1,2,3,4,5).forEach(System.out:
最大最小值
max返回流中的最大值,min返回流中的最小值。
reduce
reduce是常用的一个方法,事实上很多操作都是基于它实现的。
它有几个重载方法:
pubicOptional<
reduce(BinaryOperator<
accumulator)pubicTreduce(Tidentity,BinaryOperator<
accumulator)pubic<
U>
Ureduce(Uidentity,BiFunction<
U,?
superT,U>
accumulator,BinaryOperator<
第一个方法使用流中的第一个值作为初始值,后面两个方法则使用一个提供的初始值。
Optional<
total=Stream.of(1,2,3,4,5).reduce((x,y)->
x+y);
Integertotal2=Stream.of(1,2,3,4,5).reduce(0,(x,y)->
值得注意的是accumulator应该满足结合性(associative)。
toArray()
将流中的元素放入到一个数组中。
组合
concat用来连接类型一样的两个流。
publicstatic<
concat(Stream<
extendsT>
a,Stream<
b)
“
转换
toArray方法将一个流转换成数组,而如果想转换成其它集合类型,西需要调用collect方法,利用Collectors.toXXX方法进行转换:
T,CextendsCollection<
Collector<
T,?
C>
to