目录
1、创建int流
2、filter / map / flatMap /peek
3、mapToObj / mapToLong / mapToDouble / asLongStream / asDoubleStream
4、forEach / forEachOrdered
5、 reduce / collect
6、distinct / sorted / limit / skip
7、sum / min / max / count / average / summaryStatistics
8、anyMatch / allMatch / noneMatch
9、findFirst / findAny
10、sequential / parallel
11、iterator / boxed / toArray
12、close / onClose
Java8支持的流处理的元素类型只有4种,double、int,long和reference类型,可参考AbstractPipeline和BaseStream的类继承关系,如下:
AbstractPipeline有4个子类,分别表示流处理元素为double、int,long和reference类型的管道;这4个子类都是抽象类,每个子类下都有3个静态内部类的实现类,Head、StatefulOp、StatelessOp,其中Head用于创建一个全新的流,StatefulOp表示有状态的一类操作,StatelessOp表示无状态的一类操作,这里的有状态是指前面流元素的处理会直接影响后面流元素的处理,多线程并行处理下每次运行的结果都不相同。这4个AbstractPipeline子类的用法和实现是基本一致的,我们以IntStream为例来说明其方法使用与实现细节,本篇博客重点说明其用法。该类的类继承关系如下:
其中IntStream接口定义了int流支持的所有操作。
创建int流都是由IntStream接口类中的静态方法完成的,具体如下:
各方法的使用细节可参考如下测试用例:
@Test
public void test() throws Exception {
//包含指定的元素
// IntStream intStream=IntStream.of(1);
//返回的int流中的元素是已经排序好的
IntStream intStream=IntStream.of(1,3,2,5,4,6);
print("of",intStream);
//从11到16,不包含16
intStream=IntStream.range(11,16);
//从11到16,包含16
// intStream=IntStream.rangeClosed(11,16);
print("range",intStream);
//包含指定的元素,add方法底层也是调用accept方法,然后返回this
//返回的int流中的元素顺序与添加顺序一致
intStream=IntStream.builder().add(23).add(22).add(21).build();
print("builder", intStream);
//指定一个int生成函数
//返回的int流中的元素不排序
intStream=IntStream.generate(()->{
Random random=new Random();
return random.nextInt(100);
}).limit(6);
print("generate", intStream);
//指定一个int生成函数,前一次执行函数的结果会作为下一次调用函数的入参
//第一个参数seed就是第一次调用生成函数的入参
//返回的int流中的元素不排序
intStream=IntStream.iterate(1,x->{
int a=2*x;
if(a>16){
return a-20;
}else{
return a;
}
}).limit(6);
print("iterate", intStream);
}
@Test
public void test2() throws Exception {
IntStream streamA=IntStream.range(11,15);
IntStream streamB=IntStream.range(6,10);
//将两个IntStream 合并起来
//返回的int流的元素顺序与添加的流的元素顺序一致,不排序
IntStream streamC=IntStream.concat(streamA,streamB);
print("concat", streamC);
}
private void print(String start, IntStream intStream){
System.out.println("print for->"+start);
intStream.forEach(x->{
System.out.println(x);
});
}
上述方法的底层实现最终都是调用StreamSupport.intStream(Spliterator.OfInt spliterator, boolean parallel)方法,如下图:
filter方法会将filter函数返回false的元素从流中去除,只保留filter函数返回true的元素;map方法用于对流中的所有元素执行某个修改动作;peek方法通常用于打印流中的元素,peek函数无返回值;flatMap方法同map方法,区别在于flatMap函数的返回值是一个IntStream 而非int值,可以在返回的IntStream中包含多个元素,flatMap方法最终返回的IntStream是将每次调用flatMap函数返回的IntStream 合并后的结果。其测试用例如下:
@Test
public void test3() throws Exception {
IntStream intStream=IntStream.rangeClosed(1, 10);
//会保留过滤函数返回true的元素,此处是保留偶数
intStream=intStream.filter(x->{
return x%2==0;
}).peek(x->{ //peek方法指定的函数,以流中的元素为入参,无返回值,即不会修改元素本身
System.out.println("filter->"+x);
});
//对流中的所有元素执行某个修改动作,此处是将所有值加1
intStream=intStream.map(x->{
return x+1;
}).peek(x->{
System.out.println("map->"+x);
});
//flatMap同map,区别在于flatMap指定的函数其返回值是一个IntStream,而非一个int值,最终flatMap返回的
//IntStream是将每次调用flatMap返回的子IntStream合并后的结果
intStream=intStream.flatMap(x->{
//返回IntStream时可以返回多个元素
return IntStream.of(x+3,x+2,x+1);
}).peek(x->{
System.out.println("flatMap->"+x);
});
print("after flatMap", intStream);
}
其输出如下:
print for->after flatMap
filter->2
map->3
flatMap->6
6
flatMap->5
5
flatMap->4
4
filter->4
map->5
flatMap->8
8
flatMap->7
7
flatMap->6
6
filter->6
map->7
flatMap->10
10
flatMap->9
9
flatMap->8
8
filter->8
map->9
flatMap->12
12
flatMap->11
11
flatMap->10
10
filter->10
map->11
flatMap->14
14
flatMap->13
13
flatMap->12
12
其中因为flatMap函数返回的IntStream中包含了3个元素,所以peek方法执行了3次,每次都打印三个flatMap。从上述输出可知,只有开始执行forEach方法后才真正执行流处理动作,且原始的intStream只遍历一遍,每次遍历一个元素就按照代码声明的顺序依次执行各个处理动作。
这几个方法都是将int流转换成其他类型的流,mapTo的三个方法可以指定具体的转换函数,as的两个方法不能指定,遵循标准的int到指定类型的转换规则,其实现如下:
@Test
public void test4() throws Exception {
IntStream intStream=IntStream.rangeClosed(1, 3);
intStream.mapToObj(x->{
return new Age(x);
}).forEach(x->{
System.out.println("mapToObj->"+x);
});
//执行完mapToObj后intStream本身已经关闭了不能继续操作,只能操作其返回的新流
//此处要继续操作就必须重新初始化一个新的IntStream
intStream=IntStream.rangeClosed(1, 3);
intStream.mapToLong(x->{
return x+1;
}).forEach(x->{
System.out.println("mapToLong->"+x);
});
intStream=IntStream.rangeClosed(1, 3);
intStream.mapToDouble(x->{
return x+2;
}).forEach(x->{
System.out.println("mapToDouble->"+x);
});
//同上面的mapToLong,区别在于不能指定转换函数,而是采用标准的int到long类型的转换方法
intStream=IntStream.rangeClosed(1, 3);
intStream.asLongStream().forEach(x->{
System.out.println("asLongStream->"+x);
});
intStream=IntStream.rangeClosed(1, 3);
intStream.asDoubleStream().forEach(x->{
System.out.println("asDoubleStream->"+x);
});
}
其输出如下:
这两个方法都是用来遍历流中的元素,跟peek方法不同的是该这两个方法的返回值是void,执行此类返回值为void的方法会触发实际的流处理动作。forEachOrdered同forEach的区别在于并行流处理下,forEachOrdered会保证实际的处理顺序与流中元素的顺序一致,而forEach方法无法保证,默认的串行流处理下,两者无区别,都能保证处理顺序与流中元素顺序一致,测试用例如下:
@Test
public void test6() throws Exception {
IntStream intStream=IntStream.of(6,1,3,2,5,4).parallel();
intStream.forEach(x->{
System.out.println("forEach->"+x);
});
//forEachOrdered同forEach,区别在于并行流处理下,forEachOrdered会保证实际的处理顺序与流中元素的顺序一致
//而forEach方法无法保证,默认的串行流处理下,两者无区别,都能保证处理顺序与流中元素顺序一致
intStream=IntStream.of(6,1,3,2,5,4).parallel();
intStream.forEachOrdered(x->{
System.out.println("forEachOrdered->"+x);
});
}
其输出如下:
reduce方法用于执行类似于累加的操作,上一次调用处理函数的结果会作为入参下一次调用的入参;collect方法的效果跟forEach类似,注意其返回值是调用supplier函数的返回值,该函数在整个流处理过程中只调用一次,参考如下测试用例:
@Test
public void test7() throws Exception {
IntStream intStream=IntStream.of(6,1,3,2,5,4);
OptionalInt optionalInt=intStream.reduce((x, y)->{
System.out.println("x->"+x+",y->"+y);
return x+y;
});
System.out.println("result->"+optionalInt.getAsInt());
System.out.println("");
intStream=IntStream.of(6,1,3,2,5,4);
//同第一个reduce方法,区别在于可以指定起始的left,第一个reduce方法使用第一个元素作为起始的left
int result=intStream.reduce(2,(x, y)->{
System.out.println("x->"+x+",y->"+y);
return x+y;
});
System.out.println("result->"+result+"\n");
intStream=IntStream.of(6,1,3,2,5,4);
//同forEach方法,首先调用supplier函数生成一个值,将该值作为accumulator函数的第一个参数,accumulator函数的第二个
//参数就是流中的元素,注意第三个参数combiner无意义,可置为null
result=intStream.collect(()->{
Random random=new Random();
return random.nextInt(10);
},(x,y)->{
System.out.println("ObjIntConsumer x->"+x+",y->"+y);
},null);
//返回值是supplier函数生成的值
System.out.println("collect result->"+result+"\n");
}
其输出如下:
distinct用于对流中的元素去重,sorted用于对流中的元素升序排序,limit用于限制流中元素的个数,多余的元素会被丢弃,skip用于跳过前面指定N个元素,参考如下测试用例:
@Test
public void test8() throws Exception {
IntStream intStream=IntStream.of(6,1,1,2,5,2,3,3,4,8,6,11,10,9);
intStream.distinct() //对流中的元素去重
.sorted() //将流中的元素排序,默认升序
.skip(3) //跳过前3个元素,此处是跳过1,2,3三个元素
.limit(6) //限制流中元素的最大个数
.forEach(x->{
System.out.println(x);
});
}
其输出如下:
前面5个方法分别是获取流中元素的总和,最小值,最大值,个数和平均值,最后一个summaryStatistics方法是一次调用获取上述属性。测试用例如下:
@Test
public void test9() throws Exception {
IntStream intStream=IntStream.of(6,1,1,2,5,2,3,4);
//取流中元素的最大值
OptionalInt max=intStream.max();
System.out.println("max->"+max.getAsInt());
//同其他没有流的方法,max操作会中断流,再对该流执行相关流处理方法会报错synchronizedTest.StreamTest
intStream=IntStream.of(6,1,1,2,5,2,3,4);
//取流中元素的最小值
OptionalInt min=intStream.min();
System.out.println("max->"+max.getAsInt());
intStream=IntStream.of(6,1,1,2,5,2,3,4);
//取流中元素的平均值
OptionalDouble average=intStream.average();
System.out.println("average->"+average.getAsDouble());
intStream=IntStream.of(6,1,1,2,5,2,3,4);
//取流中元素的个数
long count=intStream.count();
System.out.println("count->"+count);
intStream=IntStream.of(6,1,1,2,5,2,3,4);
//取流中元素的总和
int sum=intStream.sum();
System.out.println("sum->"+sum);
intStream=IntStream.of(6,1,1,2,5,2,3,4);
//取流中元素的统计情形,即一次返回min,max,count等属性
IntSummaryStatistics summaryStatistics=intStream.summaryStatistics();
System.out.println(summaryStatistics.toString());
}
其输出如下:
有任何一个元素匹配,anyMatch返回true;所有元素都匹配时,allMatch返回true;没有一个元素匹配时,noneMatch返回true,参考如下测试用例:
@Test
public void test10() throws Exception {
IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
//有任何一个匹配,返回true
boolean anyMatch=intStream.anyMatch(x->{
return x%2==0;
});
System.out.println("anyMatch->"+anyMatch);
intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
//所有的都匹配,返回true
boolean allMatch=intStream.allMatch(x->{
return x%2==0;
});
System.out.println("allMatch->"+allMatch);
intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
//所有的都不匹配,返回true
boolean noneMatch=intStream.noneMatch(x->{
return x%2==0;
});
System.out.println("noneMatch->"+noneMatch);
}
输出如下:
findFirst返回流中第一个元素,findAny返回流中的任意一个元素,参考如下用例:
@Test
public void test11() throws Exception {
IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
//返回第一个元素
OptionalInt result=intStream.findFirst();
System.out.println("findFirst->"+result.getAsInt());
for(int i=0;i<6;i++) {
intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
//返回任意一个元素
result = intStream.findAny();
System.out.println("findAny->" + result.getAsInt());
}
}
sequential 返回的流是串行处理,parallel返回的流是并行处理,参考如下测试用例:
@Test
public void test12() throws Exception {
IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
long start=System.currentTimeMillis();
//并行处理
intStream.parallel().forEach(x->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+x);
});
System.out.println("parallel time->"+(System.currentTimeMillis()-start));
intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
start=System.currentTimeMillis();
//默认都是串行处理
intStream.sequential().forEach(x->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+x);
});
System.out.println("sequential time->"+(System.currentTimeMillis()-start));
}
其输出如下:
从输出可知parallel返回的流起了4个线程来处理,主线程和3个commonPool线程,这4个线程并不是严格的从一开始就并行执行,因为其执行总耗时是2061ms,而不是1s左右;串行处理是各个元素的处理耗时累加起来,8个元素,刚好是8s。
iterator 返回一个元素遍历器实现,boxed 返回一个流中元素的包装类的流,可用mapToObj实现相同的功能,toArray将流中的元素作为一个数组返回,参考如下测试用例:
@Test
public void test13() throws Exception {
IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
//返回一个包装类的流,效果相当于下面的mapToObj方法
// Stream integerStream=intStream.boxed();
Stream integerStream=intStream.mapToObj(x->{
return new Integer(x);
});
integerStream.forEach(x->{
System.out.println("boxed->"+x);
});
//返回一个元素遍历器,PrimitiveIterator是继承自Iterator
intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
PrimitiveIterator.OfInt iterator=intStream.iterator();
while (iterator.hasNext()){
System.out.println("iterator->"+iterator.next());
}
//将流中的元素转换成一个数组
intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
int[] results=intStream.toArray();
System.out.println("toArray->"+Arrays.toString(results));
}
其输出如下:
close方法会关闭流,并触发所有的onClose方法的执行;onClose方法用于注册一个回调函数,该方法返回一个新的流,可以连续调用注册多个回调函数,close触发时会按照注册的顺序依次执行。参考如下测试用例:
@Test
public void test14() throws Exception {
IntStream intStream = IntStream.of(6, 1, 1, 2, 5, 2, 3, 4);
//onClose方法的返回值是一个新的流,可以连续调用onClose,注册多个回调方法
intStream.onClose(()->{
System.out.println("intStream isClosed one ");
}).onClose(()->{
System.out.println("intStream isClosed two");
}).onClose(()->{
System.out.println("intStream isClosed three");
});
//触发onClose方法注册的多个回调方法的执行,并关闭流
intStream.close();
//流已关闭,不能执行流处理动作,forEach执行完成也会关闭流但是不会触发onClose方法的执行
// intStream.forEach(x->{
// System.out.println(x);
// });
System.out.println("main end");
}
其输出如下: