Java8新特性学习(三)- Stream类
背景及介绍
这里提到Java8的Stream类并不像Java以前版本的InputStream和OutputStream,他们是几乎不搭边的两个类。Stream类常跟集合处理一起使用,算是集合的一个增强。Stream类比前面提到的Optional类更加通用,原因在于Stream类提供更丰富的API,满足了程序中通用的集合处理方法,且使用lambda方便,较Java7减少较多的代码。
举例说明
使用Stream和lambda编码,可以提高代码效率,对于过滤取值这种常规操作,相比起java7来实现高效简单。下面举例说明,假设我们有一组求职者的简历集合,我们需要从中过滤具有3年以上工作经验,且有高并发技能的Java开发工程师的求职者,得到他们的手机号,进行面试邀约。
public class Resume {
private String name;
private Integer workingAge;
private List<String> skillList;
private String job;
private String phone;
}
//java7
List<String> phoneList = new ArrayList<>();
for (Resume resume : resumeList) {
if (resume.getWorkingAge() < 3) {
continue;
}
if (!resume.getSkillList().contains("高并发")) {
continue;
}
if (!"Java".equals(resume.getJob())) {
continue;
}
phoneList.add(resume.getPhone());
}
//java8
List<String> phoneList = resumeList.stream()
.filter(resume -> resume.getWorkingAge() >= 3)
.filter(resume -> resume.getSkillList().contains("高并发"))
.filter(resume -> "Java".equals(resume.getJob()))
.map(Resume::getPhone).collect(Collectors.toList());
重要方法介绍
java.util.Collection#stream#parallelStream
集合类对象可以直接通过stream()/paralleStream()方法创建串行流和并行流。如
List<String> phoneList = resumeList.stream()
.map(Resume::getPhone).collect(Collectors.toList());
of\generate
of()用来显式创建串行的Stream对象,有两个重载方法,一个是元素个数不限,另一个只能传一个值。基础数据类型的流,可以扩展使用IntStream、LongStream、DoubleStream.
特别说明:流只能使用一次,不然会抛异常java.lang.IllegalStateException: stream has already been operated upon or closed.
//of()
Stream<Integer> integerStream = Stream.of(1,2,3,4,5,6);
integerStream.filter(i -> i > 4).forEach(System.out::println);
这里创建的是串行的运行方式,如果想要创建并行的,可以通过Colletion#parallelStream()方法创建。of()方法暂时没有提供这个特性。
generate()方法可以自己生成控制Stream,这个方法适合用来创建常量的Stream或者随机元素的Stream。generate()是无限生成流的,需要配合limit()来使用.下面举例在1000个数字中随机抽取5个幸运中奖号码.
//generate()
Stream.generate(() -> new Random().nextInt(1000)).limit(5).forEach(System.out::println);
filter
filter会对Stream中的所有元素进行过滤,会创建一个新的Stream,其元素均符合过滤条件。如文章开头举例的简历过滤:
//java8
List<String> phoneList = resumeList.stream()
.filter(resume -> resume.getWorkingAge() >= 3)
.filter(resume -> resume.getSkillList().contains("高并发"))
.filter(resume -> "Java".equals(resume.getJob()))
.map(Resume::getPhone).collect(Collectors.toList());
map/flatMap
map和flatMap都可以被用在一个Stream对象上,且都能返回Stream.不同之处在于map操作会对每一个输入返回一个结果,然而flatMap操作会返回任意个(0个/1个/多个)值.这取决于在每个方法上的操作。
map操作接受一个Function,会将传入的每个值都调用改Function,然后得到结果后返回。而flatMap操作会对每个参数产生任意数量结果。在Java编程中,返回任意结果可能会在处理时比较麻烦,需要考虑更多的情况,因为返回值可能是一个值,Array或List。我们可以把flatMap操作可以看成是map操作 + flat操作,首先应用Function得到结果,然后将结果flat出来。而map操作相比flatMap操作则可以视为没有最后的flat操作。下面举例:
@Test
public void testFlatMap() {
List<Integer> intList1 = new ArrayList<>();
intList1.add(1);
intList1.add(2);
List<Integer> intList2 = new ArrayList<>();
intList1.add(3);
intList1.add(4);
List<Integer> list = Stream.of(intList1, intList2).flatMap(List::stream).collect(Collectors.toList());
System.out.println(list);
//[1, 2, 3, 4]
}
再举例,现在有几个单词,想过滤单词中不重复的字母。map操作无法得到,而flatMap可以得到。
//map
List<Stream<String>> sMap = Arrays.stream(words).map(word -> word.split(""))
.map(Arrays::stream).distinct()
.collect(Collectors.toList());
//flatMap
List<String> sFlatMap = Arrays.stream(words).map(word -> word.split(""))
.flatMap(Arrays::stream).distinct()
.collect(Collectors.toList());
System.out.println(sFlatMap);
//[J, A, V, G, O]
Stream map(Function<? super T, ? extends R> mapper);
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
collect
collect方法用于将Stream流中的对象转化还原为流的结果。如
List<String> asList = stringStream.collect(Collectors.toList());
Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
Map<String, Map<String, List<Person>>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));
anyMatch\allMatch
anyMatch:检测是否流中是否有任一元素满足提供Predicate.
allMatch:检测是否流中是否所有元素满足提供Predicate.
boolean anyMatch = Stream.of(1, 2, 3, 4, 5, 6).anyMatch(i -> i > 6);
System.out.println(anyMatch);//false
boolean allMatch = Stream.of(1, 2, 3, 4, 5, 6).allMatch(i -> i < 20);
System.out.println(allMatch);//true
min\max
根据参数Comparator返回流中最大\小的元素,返回类型是Optional。更多Optional信息,请查看
Java8新特性学习(二)- Optional类
Optional<Integer> optionalMin = Stream.of(1, 2, 3, 4, 5, 6).min((a, b) -> a > b ? 1 : -1);
System.out.println(optionalMin.orElse(null));//1
Optional<Integer> optionalMax = Stream.of(1, 2, 3, 4, 5, 6).max(Integer::compareTo);
System.out.println(optionalMax.orElse(null));//6
skip\limit
skip:返回除去前n个元素之外剩余元素组成的流。如果全部被抛弃,则返回一个空流。
limit:返回最大元素个数不超过n的流
Stream.of(1,2,3,4,5,6).skip(20l).forEach(System.out::print);
Stream.of(1,2,3,4,5,6).limit(3l).forEach(System.out::print);
reduce
reduce是关联累积函数对流的元素进行缩减,并返回缩减之后的Optional类型的结果。
reduce()提供了3种方式使用
1、Optional<T> reduce(BinaryOperator<T> accumulator);//返回值是Optional
2、T reduce(T identity, BinaryOperator<T> accumulator);//返回值是T,identity一样的类型
3、<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
//返回值是U,identity一样的类型,accumulator累加器,combiner:结合器
举例使用
System.out.println(Stream.of(1,2,3,4,5,6).reduce( (a, b) -> a+b).orElse(0));
System.out.println(Stream.of(1,2,3,4,5,6).reduce(0, (a, b) -> a+b));
这里会对Stream中的元素遍历每一个,进行BinaryOperator的apply操作,并最终返回一个类似合并各元素之后的结果。而我们常用的sum,max,min等操作都是一种特殊的reduce操作。可以看IntStream中min()函数的实现:
public final OptionalInt min() {
return reduce(Math::min);
}
总结
- Stream不是一种数据结构
- Stream并不会自己存储数据,而是利用原数据进行操作
- Stream只能使用一次,使用多次会抛出异常
- 具有串行和并行能力,简单高效就能写出高并发代码