Java 8新特性(二):Stream API

本文深入介绍了Java 8的Stream API,它为集合操作提供了增强,支持过滤、排序、分组和并行处理等功能。Stream API与传统集合操作不同,采用内部迭代方式,可以配合Lambda表达式简化代码。文章涵盖了创建流、使用流、数据收集和并行数据处理等关键概念,强调了Stream API在多核CPU环境下的高性能优势。
摘要由CSDN通过智能技术生成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02uVmfnf-1628481106899)(http://ohwsf74ph.bkt.clouddn.com/image/banner/java8-logo.jpeg)]

本文首发于一书生VOID的博客
原文链接:Java 8新特性(二):Stream API


本篇介绍Java 8的另一个新特性——Stream API。新增的Stream API与InputStreamOutputStream是完全不同的概念,Stream API是对Java中集合操作的增强,可以利用它进行各种过滤、排序、分组、聚合等操作。

Stream API配合Lambda表达式可以加大的简化代码,提升可读性。Stream API也支持并行操作(类似于Fork-Join),甚至不用手动编写多线程代码,Stream API已经帮我们做好了,并且能充分利用多核CPU的优势。借助Stream API和Lambda表达式,可以很容易的编写出高性能的并发处理程序。

Stream API简介

Stream API是Java 8中加入的一套新的API,主要用于处理集合操作,不过它的处理方式与传统的方式不同,称为“数据流处理”。流(Stream)类似于关系数据库的查询操作,是一种声明式操作。比如要从数据库中获取所有年龄大于20岁的用户的名称,并按照用户的创建时间进行排序,用一条SQL语句就可以搞定,不过使用Java程序实现就会显得有些繁琐,这时候可以使用流:

List<String> userNames =
        users.stream()
        .filter(user -> user.getAge() > 20)
        .sorted(comparing(User::getCreationDate))
        .map(User::getUserName)
        .collect(toList());

可以把流跟集合做一个比较。在Java中,集合是一种数据结构,或者说是一种容器,用于存放数据,流不是容器,它不关心数据的存放,只关注如何处理。可以把流当做是Java中的Iterator,不过它可比Iterator强大多了。

流与集合另一个区别在于他们的遍历方式,遍历集合通常使用for-each方式,这种方式称为外部迭代,而流使用内部迭代方式,也就是说它帮你把迭代的工作做了,你只需要给出一个函数来告诉它接下来要干什么:

// 外部迭代
List<String> list = Arrays.asList("A", "B", "C", "D");
for (String str : list) {
   
    System.out.println(str);
}

// 内部迭代
list.stream().forEach(System.out::println);

在一些比较复杂的业务场景中,要对集合做一些统计、分组的操作,如果用传统的for-each方式遍历集合,每次只能处理一个元素,并且是按顺序处理,这种方法是极其低效的。你可能会想到用多线程去并行处理,但是编写多线程代码并非易事,容易出错并且维护困难。不过在Java 8之后,你可以使用Stream API来处理这些操作。

Stream API将迭代操作封装到了内部,它会自动的选择最优的迭代方式,并且使用并行方式处理时,将集合分成多段,每一段分别使用不同的线程处理,最后将处理结果合并输出,有点类似于Fork-Join操作。

需要注意的是,流只能遍历一次,遍历结束后,这个流就被关闭掉了。如果要重新遍历,可以从数据源(集合)中重新获取一个流。如果你对一个流遍历两次,就会抛出java.lang.IllegalStateException异常:

List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // 这里会抛出java.lang.IllegalStateException异常,因为流已经被关闭

流通常由三部分构成:

  1. 数据源:数据源一般用于流的获取,比如本文开头那个过滤用户的例子中users.stream()方法。
  2. 中间处理:中间处理包括对流中元素的一系列处理,如:过滤(filter()),映射(map()),排序(sorted())。
  3. 终端处理:终端处理会生成结果,结果可以是任何不是流值,如List<String>;也可以不返回结果,如stream.forEach(System.out::println)就是将结果打印到控制台中,并没有返回。

创建流

创建流的方式有很多,具体可以划分为以下几种:

由值创建流

使用静态方法Stream.of()创建流,该方法接收一个变长参数:

Stream<Stream> stream = Stream.of("A", "B", "C", "D");

也可以使用静态方法Stream.empty()创建一个空的流:

Stream<Stream> stream = Stream.empty();

由数组创建流

使用静态方法Arrays.stream()从数组创建一个流,该方法接收一个数组参数:

String[] strs = {
   "A", "B", "C", "D"};
Stream<Stream> stream = Arrays.stream(strs);

通过文件生成流

使用java.nio.file.Files类中的很多静态方法都可以获取流,比如Files.lines()方法,该方法接收一个java.nio.file.Path对象,返回一个由文件行构成的字符串流:

Stream<String> stream = Files.lines(Paths.get("text.txt"), Charset.defaultCharset());

通过函数创建流

java.util.stream.Stream中有两个静态方法用于从函数生成流,他们分别是Stream.generate()Stream.iterate()

// iteartor
Stream.iterate(0, n -> n + 2).limit(51).forEach(System.out::println);

// generate
Stream.generate(() -> "Hello Man!").limit(10).forEach(System.out::println);

第一个方法会打印100以内的所有偶数,第二个方法打印10个Hello Man!。需要注意的是,这两个方法生成的流都是无限流,没有固定大小,可以无穷的计算下去,在上面的代码中我们使用了limit()来避免打印无穷个值。

一般来说,iterate()用于生成一系列值,比如生成以当前时间开始之后的10天的日期:

Stream.iterate(LocalDate.now(), date -> date.plusDays(1)).limit(10).forEach(System.out::println);

generate()方法用于生成一些随机数,比如生成10个UUID:

Stream.generate(() -> UUID.randomUUID().toString(
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值