Java8函数式编程 (一) 数据流和lambda表达式

JDK 1.8中引入了函数式编程(functional programming,FP),如果您已习惯OOP,一定会感到困惑:什么是函数式编程?这样的编程模式有什么好处?

本文将通过简单的实例令读者对函数式编程有一个大体的了解。

我们知道OOP是以类为基础的,程序中必须首先抽象和定义class。那么FP创建的基础是什么?或者说在Java 8中,至少需要了解什么知识点才能实现基本的函数式编程呢?

本文将首先介绍在Java 8中使用FP所需的基本知识点:

  • Lambda表达式
  • 数据流

基本实例

Map<String, List<String>> phoneNumbers = new HashMap<String, List<String>>();

phoneNumbers.put("Zhang Jin", Arrays.asList("3232312323", "8933555472"));
phoneNumbers.put("Li Ming", Arrays.asList("12323344", "492648333"));
phoneNumbers.put("Li Guoping", Arrays.asList("77323344", "938448333"));

Map<String, List<String>> filteredNumbers = phoneNumbers.entrySet().stream()
    .filter(x -> x.getKey().contains("Li"))
    .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));

filteredNumbers.forEach((key, value) -> {
    System.out.println("Name: " + key + ": ");
    value.forEach(System.out::println);
});

上半部分的代码创建了一个从人名到此人所有电话号码的Map,比较简单,但接下来的部分对刚接触Java 8的读者不是一眼就能理解,我们一会儿来详细解释一下。

数据状态不变原则

在分析上面的code前,先来看看FP与我们所熟知的OOP的最大不同。

在面向对象编程时,我们定义类和对象,并使用方法或表达式来执行命令。这些方命令通常会改变程序的数据状态:

Integer x = 0;
x++;

比如上面的代码,当执行x++后,x的值产生了变化,旧数据被新数据所取代。

相反,在函数式编程中,尽管我们也需要通过方法来执行命令,但命令本身并不会改变程序已有的数据。简单地说就是函数调用对外界不产生副作用,并总是输出新的变量作为调用结果,比如下面的javascript:

function max(a, b) {
    return a > b ? a : b;
}

var x = 10;
var y = 5;
var maximum = max(x, y);

此处max并不改变和依赖于外部的x和y,同时每次返回的结果都是一个全新的变量。

代码分析

现在我们回到之前的代码:

// create a map, filter it by key values
Map <String, List<String>> filteredNumbers = phoneNumbers.entrySet().stream()
    .filter(x -> x.getKey().contains("Li"))
    .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));

这段代码首先调用entrySet()来获得phoneNumbers的entries集合,其中每个entry都由一个键值对组成。

接着,在得到的集合上调用stream()方法,该方法会创建一个数据流 (java.util.stream.Stream)。数据流(stream)是Java 8引入的新概念,参考Javadoc,可以将它理解为是一种可以在其上执行顺序或并行聚合操作的数据序列。这些操作可以包括过滤,修改,不同类型的转换等。

得到stream后,代码调用了filter(),filter通过给定的条件来过滤数据。此处的过滤条件是:

x -> x.getKey().contains("Li")

该处采用了Java 8引入的lambda表达式,我们可以把lambda表达式看成一个简洁的匿名函数,->左边的x是输入参数,->右边是需要执行的命令。这里的lambda表达式又被称作predicate,它是一个返回boolean类型的函数,stream中的每个element都会被代入该predicate,通过运算出的结果来决定是否在filter()重新返回的stream中被留下或移除。

下一个方法是collect(),可以看到,这里的方法调用都是链式的,原因在于这些方法都归属于Stream接口,所以使用起来非常方便简洁。

collect()方法的用途很简单,它先从stream内获得数据(这里是java.util.Map.Entry),再将数据转变成通常的java collection,比如List, Map, Set等数据结构。这样就重新将stream转变成了我们所熟悉的类型。

Collection的遍历

来看最后一部分代码:

filteredNumbers.forEach((key, value) -> {
    System.out.println("Name: " + key + ": ");
    value.stream().forEach(System.out::println);
});

该代码打印出所有的结果,但并没有使用循环,而是用了Java 8引入的forEach方法。

有了前面的基础,可以很容易看懂:forEach接收一个lambda表达式,filteredNumbers的每个element都讲执行该表达式。由于filteredNumbers是一个map,所以这里lambda表达式参数变成了key, value pair:

// expression takes two parameters
(key, value) - > {
    // print person’s name
    System.out.println("Name: " + key + ": ");
    // iterate over the person’s phone numbers and print each of them
    value.forEach(System.out::println);
}

由于value本身是一个List,所以又在其上调用了forEach。

至此代码的输出为:

Name: Li Ming:
12323344
492648333Name: Li Guoping:77323344938448333

学到的知识点 (以及问题)

本文表述的几个关键点:

  • Java 8通过数据流和lambda表达式使函数式编程成为可能。
  • Lambda表达式和匿名函数十分相似。
  • Stream是一种可以在其上执行顺序或并行聚合操作的数据序列,这些操作会作用于序列里的每个元素。
  • 函数式编程模式中的操作倾向于不依赖和不改变已有的数据状态。
  • 数据流操作很简洁性 — 如果不考虑执行效率的话。

但现在您可能会想:

  • 函数式编程写出来的东西理解起来似乎更困难 — 我还是喜欢原来的写法, 尽管它要啰嗦很多。
  • streams操作的执行效率高吗?
  • 如果不改变数据状态,程序应该怎么写才好呢?

这些问题我会在后续的章节中进一步讨论,尽情期待。

时间: 2025-01-05 02:12:47

Java8函数式编程 (一) 数据流和lambda表达式的相关文章

Python笔记第3章,模块和模块的常用方法,函数式编程yield,三元运算Lambda表达式,内置涵数,常用模块

<--目录--> 模块和模块的常用方法 函数式编程 yield 三元运算 Lambda表达式 内置涵数 常用模块 [模块和模块的常用方法]至关重要的__init__.py    #包一定要有这个__init__.py文件,他才是包,这样才能通过form 包名 import 模块名(.py文件名)来去引用这个包的某个模块的某个函数功能 判断是否为主文件:__name__ if __name__ == '__main__' 当前文件路径: __file__ 当前文件描述: __doc__ 1)if

漫漫人生路,学点Jakarta基础-Java8函数式编程

接口默认方法 Java8版本以后新增了接口的默认方法,不仅仅只能包含抽象方法,接口也可以包含若干个实例方法.在接口内定义实例方法(但是注意需要使用default关键字) 在此定义的方法并非抽象方法,而是具有特定逻辑的实例方法. 举例说明:定义接口Animal,其中包含默认方法eat(). /** * Created by zjc on 2018/4/9. */ public interface Animal { void call(); default void eat() { System.o

Java8 新特性----函数式接口,以及和Lambda表达式的关系

这里来讲解一下Java8 新特性中的函数式接口, 以及和Lambda 表达式的关系.看到过很多不少介绍Java8特性的文章,都会介绍到函数式接口和lambda表达式,但是都是分别介绍,没有将两者的关系说明清楚,在这里,把自己的理解整理如下: 一.函数式接口: 函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method).定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数.从另一个方面说,一旦我

Java8新特性Stream API与Lambda表达式详解(1)

1 为什么需要Stream与Lambda表达式? 1.1  为什么需要Stream Stream作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream.Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作(aggr

Java8函数式编程(一):Lambda表达式类型与常用函数接口

[TOC] 1 前言 最近在看一些开源项目的源码,函数式编程风格的代码无处不在,所以得要好好学一下了. 2 Lambda表达式类型 无参数: Runnable noArguments = () -> System.out.println("Hello World!"); noArguments.run(); 一个参数: UnaryOperator<Boolean> oneArgument = x -> !x; System.out.println(oneArgu

[三]java8 函数式编程Stream 概念深入理解 Stream 运行原理 Stream设计思路

Stream的概念定义   官方文档是永远的圣经~ 表格内容来自https://docs.oracle.com/javase/8/docs/api/   Package java.util.stream  一节部分原文内容的翻译 int sum = widgets.stream() .filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum(); 流操作被划分为中间和终端操作,并组合成流管道. 一条Stream管道由

Java8函数式编程的宏观总结

1.java8优势通过将行为进行抽象,java8提供了批量处理数据的并行类库,使得代码可以在多核CPU上高效运行. 2.函数式编程的核心使用不可变值和函数,函数对一个值进行处理,映射成另一个值. 3.Lambda表达式一种紧凑的.传递行为的方式. 4. 静态类型语言java8依旧是静态类型语言,javac依旧会在编译时,对参数类型进行检查. 5.函数接口只有一个抽象方法的接口,用作lamdba表达式的类型. 6.对核心类库的改进-Stream实现机制 整个过程:一系列惰性求值方法+最后一个及早求

Java8函数式编程-包教包会系列(一)

Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,距离现在已经有很长一段时间了,Java10也在2018年 3月 21日正式发布,并且Oracle宣布Java 语言将每隔 6 个月提供一次更新.Java语言不断更新迭代,然而很多Java程序员缺没有跟上技术更新的步伐!所以写下这篇系列文章,希望可以帮助大家入门Java8!话不多说,开始发车! 前言 宝刀未老的Java 1995年Sun公司推出的Java语言,从第一个版本诞生到现在已经有二十多年的了.时间若白驹过隙,转瞬即逝

java8新增特性(一)---Lambda表达式

Lambda表达式也成为闭包,是java语言层次上的改变,Lambda同意把函数作为一个方法的參数(函数作为參数传递进方法中),或者把代码看成数据.函数式程序猿对这一概念非常熟悉. 在JVM平台上有非常多语言(groovy,scala等等)从一開始就有Lambda,可是程序猿不得不使用匿名类来取代lambda. 看一下jdk7之前实现字符串排序: package com.panther.dong.java8.lambda; import java.util.Arrays; import java