从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java数据类型

    • Java 多版本配置
    • 面向对象

    • Java核心类

    • IO

    • Java与时间

    • 异常处理

    • 哈希和加密算法

    • Java8新特性

      • 函数式编程与 Lambda
        • 函数式编程
        • Java 的方法
        • Lambda 表达式
        • FunctionalInterface
        • 方法引用
        • 引用实例方法
        • 引用构造方法
        • 参考
      • Stream 介绍
      • 创建 Stream
      • 操作 Stream
      • Optional
    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • Java8新特性
2023-03-13
目录

函数式编程与 Lambda

# 函数式编程与 Lambda

本文主要参考廖雪峰老师的教程函数式编程 (opens new window),并自己动手实践了一遍。

# 函数式编程

我们先看看什么是函数。函数是一种最基本的任务,一个大型程序就是一个顶层函数调用若干底层函数,这些被调用的函数又可以调用其他函数,即大任务被一层层拆解并执行。所以函数就是面向过程的程序设计的基本单元。

Java 不支持单独定义函数,但可以把静态方法视为独立的函数,把实例方法视为自带 this 参数的函数。

而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

我们首先要搞明白计算机(Computer)和计算(Compute)的概念。

在计算机的层次上,CPU 执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。

而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。

对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如 C 语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如 Lisp 语言。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

函数式编程最早是数学家阿隆佐·邱奇 (opens new window)研究的一套函数变换逻辑,又称 Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为 Lambda 计算。

Java 平台从 Java 8 开始,支持函数式编程。

函数式编程是一个比较复杂的学科,本系列课程暂时不会深入去讲解,先简单了解下 Java 里关于函数式编程的新特性

# Java 的方法

在了解 Lambda 之前,我们先回顾一下 Java 的方法。

Java 的方法分为实例方法以及静态方法,例如 Integer 定义的 equals() 方法和 parseInt() 方法:

public final class Integer {
    boolean equals(Object o) {
        ...
    }

    public static int parseInt(String s) {
        ...
    }
}
1
2
3
4
5
6
7
8
9

无论是实例方法,还是静态方法,本质上都相当于过程式语言的函数,只不过 Java 的实例方法隐含地传入了一个 this 变量,即实例方法总是有一个隐含参数 this。

函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是 Lambda 演算,所以我们经常把支持函数式编程的编码风格称为 Lambda 表达式。

# Lambda 表达式

在 Java 程序中,我们经常遇到一大堆单方法接口,即一个接口只定义了一个方法:

  • Comparator
  • Runnable
  • Callable

以 Comparator 为例,我们想要调用 Arrays.sort() 时,可以传入一个 Comparator 实例,以匿名类方式编写如下:

String[] array = {"peter", "jxl", "hello"};
Arrays.sort(array, new Comparator<String>() {
  public int compare(String s1, String s2){
    return s1.compareTo(s2);
  }
});
1
2
3
4
5
6

上述写法非常繁琐。

从 Java 8 开始,我们可以用 Lambda 表达式替换单方法接口。改写上述代码如下:

String[] array = new String[] { "peter", "jxl", "hello"};
Arrays.sort(array, (s1, s2) -> {
    return s1.compareTo(s2);
});
1
2
3
4

观察 Lambda 表达式的写法,它只需要写出方法定义:

(s1, s2) -> {
    return s1.compareTo(s2);
}
1
2
3

其中,参数是 (s1, s2),参数类型可以省略,因为编译器可以自动推断出 String 类型。-> { ... } 表示方法体,所有代码写在内部即可。Lambda 表达式没有 class 定义,因此写法非常简洁。

如果只有一行 return xxx 的代码,完全可以用更简单的写法:

Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));
1

返回值的类型也是由编译器自动推断的,这里推断出的返回值是 int,因此,只要返回 int,编译器就不会报错。

# FunctionalInterface

我们把只定义了单方法的接口称之为 FunctionalInterface,用注解 @FunctionalInterface 标记。例如,Callable 接口:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
1
2
3
4

再来看 Comparator 接口的源码:

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    default Comparator<T> thenComparing(Comparator<? super T> other) {
        ...
    }
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

虽然 Comparator 接口有很多方法,但只有一个抽象方法 int compare(T o1, T o2),其他的方法都是 default 方法或 static 方法。另外注意到 boolean equals(Object obj) 是 Object 定义的方法,不算在接口方法内。因此,Comparator 也是一个 FunctionalInterface。

# 方法引用

使用 Lambda 表达式,我们就可以不必编写 FunctionalInterface 接口的实现类,从而简化代码:

Arrays.sort(array, (s1, s2) -> {
    return s1.compareTo(s2);
});
1
2
3

实际上,除了 Lambda 表达式,我们还可以直接传入方法引用。例如:

import java.util.Arrays;

public class LearnLambda2 {
  public static void main(String[] args) {
    String[] array = {"Apple", "Orange", "Banana", "Lemon"};
    Arrays.sort(array, LearnLambda2::cmp);  //Apple, Banana, Lemon, Orange
    System.out.println(String.join(", ", array));
  }

  static int cmp(String s1, String s2){
    return s1.compareTo(s2);
  }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意第 6 行,Arrays.sort() 中直接传入了静态方法 cmp 的引用,用 LearnLambda2::cmp 表示。

因此,所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。

因为 Comparator<String> 接口定义的方法是 int compare(String, String),和静态方法 int cmp(String, String) 相比,除了方法名外,方法参数一致,返回类型相同,因此,我们说两者的方法签名一致,可以直接把方法名作为 Lambda 表达式传入

注意:在这里,方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系。

# 引用实例方法

上一小节,我们引用的是静态方法,可不可以改成实例方法呢?我们来测试下,可以将上一小节的代码改成:

Arrays.sort(array, String::compareTo);
1

不但可以编译通过,而且运行结果也是一样的,这说明 String.compareTo() 方法也符合 Lambda 定义。

观察 String.compareTo() 的方法定义:

public final class String {
    public int compareTo(String o) {
        ...
    }
}
1
2
3
4
5

这个方法的签名只有一个参数,为什么和 int Comparator<String>.compare(String, String) 能匹配呢?

因为实例方法有一个隐含的 this 参数,String 类的 compareTo() 方法在实际调用的时候,第一个隐含参数总是传入 this,相当于静态方法:

public static int compareTo(this, String o);
1

所以,String.compareTo() 方法也可作为方法引用传入。

# 引用构造方法

除了可以引用静态方法和实例方法,我们还可以引用构造方法。

我们来看一个例子:如果要把一个 List<String> 转换为 List<Person>,应该怎么办?

class Person{
  String name;
  public Person(String name){
    this.name = name;
  }
}

List<String> names = List.of("peter", "JXL", "Hello");
List<Person> persons = new ArrayList<>();
1
2
3
4
5
6
7
8
9

传统的做法是先定义一个 ArrayList<Person>,然后用 for 循环填充这个 List:

List<String> names = List.of("peter", "JXL", "Hello");
List<Person> persons = new ArrayList<>();
for (String name : names) {
  persons.add(new Person(name));
}
1
2
3
4
5

但有一个更简单的做法,引用 Person 的构造方法:

List<Person> persons2 = names.stream().map(Person::new).collect(Collectors.toList());
1

后面我们会讲到 Stream 的 map() 方法,这里有个印象就行。现在我们看到,这里的 map() 需要传入的 FunctionalInterface 的定义是:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
1
2
3
4

把泛型对应上,就是方法签名 Person apply(String),即传入参数 String,返回类型 Person。而 Person 类的构造方法恰好满足这个条件,因为构造方法的参数是 String,而构造方法虽然没有 return 语句,但它会隐式地返回 this 实例,类型就是 Person,因此,此处可以引用构造方法。构造方法的引用写法是 类名::new,因此,此处传入 Person::new。

# 参考

Lambda 的基本概念:Lambda 基础 - 廖雪峰的官方网站 (opens new window)

Lambda 在工作中的使用:公司新来了一个同事,把 Lambda 表达式运用的炉火纯青! (opens new window)

上次更新: 2024/10/1 17:11:02
SSL 和 TLS 协议介绍
Stream 介绍

← SSL 和 TLS 协议介绍 Stream 介绍→

最近更新
01
吐槽一下《僵尸校园》
05-15
02
2025 年 4 月记
04-30
03
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式