java8 Stream API

本文深入探讨了Java 8的Stream API,包括接口Stream的使用,基本示例,中间操作如distinct、sorted、skip/limit、peek等,终端操作如max/min、count、forEach、reduce,以及如何构建和收集Stream。文章还详细介绍了分组操作,如按条件过滤、排序、计算统计信息等,并提供了多级分组和分区的实例。
摘要由CSDN通过智能技术生成


前言

针对常见的集合数据处理,Java 8引入了一套新的类库,位于包java.util.stream下,称为Stream API。这套API操作数据的思路不同于我们之前介绍的容器类API,它们是函数式的,非常简洁、灵活、易读。


一、接口Stream

接口Stream类似于一个迭代器,但提供了更为丰富的操作,Stream API的主要操作就定义在该接口中。
Java 8给Collection接口增加了两个默认方法,它们可以返回一个Stream,如下所示:

 default Stream<E> stream() {
   
     return StreamSupport.stream(spliterator(), false);
 }
 default Stream<E> parallelStream() {
   
     return StreamSupport.stream(spliterator(), true);
 }

stream()返回的是一个顺序流,parallelStream()返回的是一个并行流
顺序流就是由一个线程执行操作,而并行流背后可能有多个线程并行执行。

并行流内部会使用Java 7引入的fork/join框架,即处理由fork和join两个阶段组成,fork就是将要处理的数据拆分为小块,多线程按小块进行并行计算,join就是将小块的计算结果进行合并。

二、基本示例

1.基本过滤

返回学生列表中90分以上的,传统上的代码一般是这样:

List<Student> above90List = new ArrayList<>();
for (Student t : students) {
   
    if (t.getScore() > 90) {
   
        above90List.add(t);
    }
}
log.info(above90List.toString());//[Student{name='wangwu', score=98.0}]

使用Stream API,代码可以这样:

List<Student> above90List = students.stream()
        .filter(t -> t.getScore() > 90)
        .collect(Collectors.toList());
log.info(above90List.toString());//[Student{name='wangwu', score=98.0}]

2.基本转换

根据学生列表返回名称列表,传统上的代码一般是这样:

List<String> nameList = new ArrayList<>(students.size());
for (Student t : students) {
   
    nameList.add(t.getName());
}
log.info(nameList.toString()); // [zhangsan, lisi, wangwu]

使用Stream API,代码可以这样:

List<String> nameList = students.stream()
        .map(Student::getName)
        .collect(Collectors.toList());
log.info(nameList.toString()); // [zhangsan, lisi, wangwu]

这里使用了Stream的map函数,它的参数是一个Function函数式接口,这里传递了方法引用。

三、中间操作

不实际触发执行、用于构建流水线、返回Stream的操作称为中间操作(intermediate operation)

Stream API的中间操作有filter、map、distinct、sorted、skip、limit、peek、mapToLong、mapToInt、mapToDouble、flatMap等

1.distinct

distinct返回一个新的Stream,过滤重复的元素,只留下唯一的元素,是否重复是根据equals方法来比较的,
distinct可以与其他函数(如filter、map)结合使用。比如,返回字符串列表中长度小于3的字符串、转换为小写、只保留唯一的,代码可以为:

List<String> list = Arrays.asList("abc", "def", "hello", "Abc");
List<String> retList = list.stream()
        .filter(s -> s.length() <= 3).map(String::toLowerCase).distinct()
        .collect(Collectors.toList());
log.info(retList.toString()); // [abc, def]

虽然都是中间操作,但distinct与filter和map是不同的。
filter和map都是无状态的,对于流中的每一个元素,处理都是独立的,处理后即交给流水线中的下一个操作;
distinct不同,它是有状态的,在处理过程中,它需要在内部记录之前出现过的元素,如果已经出现过,即重复元素,它就会过滤掉,不传递给流水线中的下一个操作。
对于顺序流,内部实现时,distinct操作会使用HashSet记录出现过的元素,如果流是有顺序的,需要保留顺序,会使用LinkedHashSet。

2.sorted

有两个sorted方法:

Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)

它们都对流中的元素排序,都返回一个排序后的Stream。
第一个方法假定元素实现了Comparable接口,第二个方法接受一个自定义的Comparator。
比如,过滤得到80分以上的学生,然后按分数从高到低排序,分数一样的按名称排序,代码为:

List<Student> list = students.stream()
        .filter(t -> t.getScore() > 80)
        .sorted(Comparator.comparing(Student::getScore).reversed().thenComparing(Student::getName))
        .collect(Collectors.toList());
log.info(list.toString()); 
// [Student{name='wangwu', score=98.0}, Student{name='lisi', score=89.0}, Student{name='zhangsan', score=89.0}]

这里,使用了Comparator的comparing、reversed和thenComparing构建了Comparator。
与distinct一样,sorted也是一个有状态的中间操作,在处理过程中,需要在内部记录出现过的元素。
其不同是,每碰到流中的一个元素,distinct都能立即做出处理,要么过滤,要么马上传递给下一个操作;
sorted需要先排序,为了排序,它需要先在内部数组中保存碰到的每一个元素,到流结尾时再对数组排序,然后再将排序后的元素逐个传递给流水线中的下一个操作。

3.skip/limit

Stream<T> skip(long n) // skip跳过流中的n个元素,如果流中元素不足n个,返回一个空流
Stream<T> limit(long maxSize) // limit限制流的长度为maxSize

比如,将学生列表按照分数排序,返回第3名到第5名,代码为:

List<Student> list = students.stream()
        .sorted(Comparator.comparing(Student::getScore).reversed())
        .skip(2).limit(3)
        .collect(Collectors.toList());
log.info(list.toString()); // [Student{name='lisi', score=89.0}]

skip和limit都是有状态的中间操作。
对前n个元素,skip的操作就是过滤,对后面的元素,skip就是传递给流水线中的下一个操作。
limit的一个特点是:它不需要处理流中的所有元素,只要处理的元素个数达到maxSize,后面的元素就不需要处理了,这种可以提前结束的操作称为短路操作

skip和limit只能根据元素数目进行操作,Java 9增加了两个新方法,相当于更为通用的skip和limit:

//通用的skip,在谓词返回为true的情况下一直进行skip操作,直到某次返回false
default Stream<T> dropWhile(Predicate<? super T> predicate)
//通用的limit,在谓词返回为true的情况下一直接受,直到某次返回false
default Stream<T> takeWhile(Predicate<? super T> predicate)

4.peek

Stream<T> peek(Consumer<? super T> action)

它返回的流与之前的流是一样的,没有变化,但它提供了一个Consumer,会将流中的每一个元素传给该Consumer。
这个方法的主要目的是支持调试,可以使用该方法观察在流水线中流转的元素,比如:

List<String> above90Names = students.stream()
        .filter(t -> t.getScore() > 90)
        .peek(System.out::println) // Student{name='wangwu', score=98.0}
        .map(Student::getName)
        .peek(System.out::println) // wangwu
        .collect(Collectors.toList());

5.mapToLong/mapToInt/mapToDouble

map函数接受的参数是一个Function<T, R>,为避免装箱/拆箱,提高性能,Stream还有如下返回基本类型特定流的方法:

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)

DoubleStream/IntStream/LongStream是基本类型特定的流,有一些专门的更为高效的方法。比如,求学生列表的分数总和,代码为:

double sum = students.stream()
        .mapToDouble(Student::getScore)
        .sum();
log.info("" + sum); // 276.0

6.flatMap

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

它接受一个函数mapper,对流中的每一个元素,mapper会将该元素转换为一个流Stream,然后把新生成流的每一个元素传递给下一个操作。比如:

List<String> lines = Arrays.asList("hello abc", "123 456");
List<String> words = lines.stream()
        .flatMap(line -> Arrays.stream(line.split("\\s+")))
        .collect(Collectors.toList());
log.info(words.toString()); // [hello, abc, 123, 456]

这里的mapper将一行字符串按空白符分隔为了一个单词流,Arrays.stream可以将一个数组转换为一个流,flatMap完成了一个1到n的映射。

四、终端操作

触发实际执行、返回具体结果的操作称为终端操作(terminal operation)

Stream API的终端操作有collect、max、min、count、allMatch、anyMatch、noneMatch、findFirst、findAny、forEach、toArray、reduce等。

1.max/min

Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值