Java中的数据流和函数式编程

java中的数据流和函数式编程

当 Java SE 8(又名核心 Java 8)在 2014 年被推出时,它引入了一些更改,从根本上影响了用它进行的编程。这些更改中有两个紧密相连的部分:流 API 和函数式编程构造。本文使用代码示例,从基础到高级特性,介绍每个部分并说明它们之间的相互作用。

学习如何使用 Java 8 中的流 API 和函数式编程结构。

当 Java SE 8(又名核心 Java 8)在 2014 年被推出时,它引入了一些更改,从根本上影响了用它进行的编程。这些更改中有两个紧密相连的部分:流 API 和函数式编程构造。本文使用代码示例,从基础到高级特性,介绍每个部分并说明它们之间的相互作用。

基础特性

流 API 是在数据序列中迭代元素的简洁而高级的方法。包 java.util.stream 和 java.util.function 包含了用于流 API 和相关函数式编程构造的新库。当然,代码示例胜过千言万语。

下面的代码段用大约 2,000 个随机整数值填充了一个 List:

Random rand = new Random2();

List<Integer> list = new ArrayList<Integer>();          // 空 list

for (int i = 0; i < 2048; i++) list.add(rand.nextInt()); // 填充它

另外用一个 for 循环可用于遍历填充列表,以将偶数值收集到另一个列表中。

流 API 提供了一种更简洁的方法来执行此操作:

List <Integer> evens = list

    .stream()                      // 流化 list

    .filter(n -> (n & 0x1) == 0)  // 过滤出奇数值

    .collect(Collectors.toList()); // 收集偶数值

这个例子有三个来自流 API 的函数:

stream 函数可以将集合转换为流,而流是一个每次可访问一个值的传送带。流化是惰性的(因此也是高效的),因为值是根据需要产生的,而不是一次性产生的。

filter 函数确定哪些流的值(如果有的话)通过了处理管道中的下一个阶段,即 collect 阶段。filter 函数是 高阶的higher-order,因为它的参数是一个函数 —— 在这个例子中是一个 lambda 表达式,它是一个未命名的函数,并且是 Java 新的函数式编程结构的核心。

lambda 语法与传统的 Java 完全不同:

n -> (n & 0x1) == 0

箭头(一个减号后面紧跟着一个大于号)将左边的参数列表与右边的函数体分隔开。参数 n虽未明确类型,但也可以明确。在任何情况下,编译器都会发现 n 是个 Integer。如果有多个参数,这些参数将被括在括号中,并用逗号分隔。

在本例中,函数体检查一个整数的最低位(最右)是否为零,这用来表示偶数。过滤器应返回一个布尔值。尽管可以,但该函数的主体中没有显式的 return。如果主体没有显式的 return,则主体的最后一个表达式即是返回值。在这个例子中,主体按照 lambda 编程的思想编写,由一个简单的布尔表达式 (n & 0x1) == 0 组成。

collect 函数将偶数值收集到引用为 evens 的列表中。如下例所示,collect 函数是线程安全的,因此,即使在多个线程之间共享了过滤操作,该函数也可以正常工作。

方便的功能和轻松实现多线程

在生产环境中,数据流的源可能是文件或网络连接。为了学习流 API, Java 提供了诸如 IntStream 这样的类型,它可以用各种类型的元素生成流。这里有一个 IntStream 的例子:

IntStream                          // 整型流

    .range(1, 2048)                // 生成此范围内的整型流

    .parallel()                    // 为多个线程分区数据

    .filter(i -> ((i & 0x1) > 0))  // 奇偶校验 - 只允许奇数通过

    .forEach(System.out::println); // 打印每个值

IntStream 类型包括一个 range 函数,该函数在指定的范围内生成一个整数值流,在本例中,以 1 为增量,从 1 递增到 2048。parallel 函数自动划分该工作到多个线程中,在各个线程中进行过滤和打印。(线程数通常与主机系统上的 CPU 数量匹配。)函数 forEach 参数是一个方法引用,在本例中是对封装在 System.out 中的 println 方法的引用,方法输出类型为 PrintStream。方法和构造器引用的语法将在稍后讨论。

由于具有多线程,因此整数值整体上以任意顺序打印,但在给定线程中是按顺序打印的。例如,如果线程 T1 打印 409 和 411,那么 T1 将按照顺序 409-411 打印,但是其它某个线程可能会预先打印 2045。parallel 调用后面的线程是并发执行的,因此它们的输出顺序是不确定的。

map/reduce 模式

map/reduce 模式在处理大型数据集方面变得很流行。一个 map/reduce 宏操作由两个微操作构成。首先,将数据分散(映射mapped)到各个工作程序中,然后将单独的结果收集在一起 —— 也可能收集统计起来成为一个值,即归约reduction。归约可以采用不同的形式,如以下示例所示。

下面 Number 类的实例用 EVEN 或 ODD 表示有奇偶校验的整数值:

public class Number {

    enum Parity { EVEN, ODD }

    private int value;

    public Number(int n) { setValue(n); }

    public void setValue(int value) { this.value = value; }

    public int getValu

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值