阅读 100

JAVA Stream杂谈(二): 函数式编程的概念

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

函数式编程functional programming)或称函数程序设计泛函编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算为该语言最重要的基础。而且,λ演算的函数可以接受函数作为输入参数和输出返回值。

比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

1. 概念

1.1 一等公民 First-class_citizen

  • 在函数式编程中,函数被视为一等公民,这意味着它们可以绑定到名称(包括本地标识符),作为参数传递,并从其他函数返回,就像任何其他数据类型一样。这允许以声明性和可组合的风格编写程序,其中小功能以模块化方式组合

1.2 一等函数 First-class_function

  • 在具有一等函数的语言中,函数的名称没有任何特殊的地位;它们被视为普通变量带有函数类型。这个词是克里斯托弗·斯特雷奇在 1960 年代中期在“functions as first-class citizens”(函数作为一等公民)的背景下创造的。
  • 一等函数是函数式编程风格的必要条件,其中使用高阶函数是一种标准做法。
  • 高阶函数与一等函数密切相关,因为高阶函数和一等函数都允许函数作为其他函数的参数和结果。两者之间的区别很微妙:“高阶”描述了对其他函数进行运算的函数的数学概念,而“一等”是编程语言实体的计算机科学术语,它们的使用没有限制(因此,一级函数可以出现在程序中任何其他一级实体(如数字)可以出现的地方,包括作为其他函数的参数和返回值)。

1.3 # 高阶函数 Higher-order_function

  • 在数学和计算机科学中,高阶函数是至少执行以下一项操作的函数:

    • 将一个或多个函数作为参数(即过程参数)
    • 返回一个函数作为其结果。
  • 高阶函数twice采用一个函数,并将该函数应用于某个值两次。如果twice必须多次应用这个函数,它最好应该返回一个函数而不是一个值。这符合“Don't repeat yourself”(不要重复自己)的原则。

import java.util.function.*;

class Main {
    public static void main(String[] args) {
        Function<IntUnaryOperator, IntUnaryOperator> twice = f -> f.andThen(f);

        IntUnaryOperator plusThree = i -> i + 3;

        var g = twice.apply(plusThree);

        System.out.println(g.applyAsInt(7)); // 13
    }
}
复制代码

1.4 纯函数 Pure_function

纯函数没有副作用(内存或 I/O)。这意味着纯函数有几个有用的属性,其中许多可用于优化代码。

  • 如果不使用纯表达式的结果,则可以将其删除而不影响其他表达式。
  • 如果使用不会引起副作用的参数调用纯函数,则结果相对于该参数列表是常量(有时称为引用透明性或幂等性),即,再次使用相同参数调用纯函数会返回相同的结果。
  • 如果两个纯表达式之间没有数据依赖,那么它们的顺序可以颠倒,或者它们可以并行执行并且它们不会相互干扰(换句话说,任何纯表达式的计算都是线程安全的)。
  • 如果整个语言不允许副作用,那么可以使用任何评估策略;这使编译器可以自由地重新排序或组合程序中表达式的计算。

2. 回到java来,随便谈谈

λ表达式的类型是什么?某些语言使用函数值函数对象来表示 λ表达式,但不适用于 Java 语言。

相反,Java 使用函数式接口来表示 lambda 表达式类型。乍一看似乎很奇怪,但实际上这是确保与旧版本 Java 向后兼容的有效方法。

2.1 指令式转变到函数式

下面这段代码应该很熟悉:

Thread thread = new Thread(new Runnable() {
  public void run() {
    System.out.println("In another thread");
  }
});

thread.start();

System.out.println("In main");
复制代码

本Thread类和它的构造在Java 1.0中引入,远远超过20年前。从那以后,构造函数没有改变。将匿名实例传递Runnable给构造函数是传统的做法。但是从 Java 8 开始,您可以选择传递 lambda 表达式:

Thread thread = new Thread(() ‑> System.out.println("In another thread"));
复制代码

Thread类的构造函数需要一个实现Runnable。 在这种情况下,我们传递的是一个 lambda 表达式,而不是传递一个对象。我们可以选择使用各种方法和构造函数传递 lambda 表达式,包括一些在 Java 8 之前创建的。它之所以有效,是因为 lambda 表达式在 Java 中表示为函数式接口。

2.2 Java的函数式接口有三个重要规则:

  • 一个功能接口只有一个抽象方法。
  • 任何也是Object类中公共方法的抽象方法都不算作该方法。
  • 功能接口可能有默认方法和静态方法。

任何满足这个单一抽象方法规则的接口都将被自动视为函数式接口。这包括传统接口,例如RunnableCallable

2.3 小结

lambda 表达式成为一种函数式接口的设计决策促进了 Java 8 和 Java 早期版本之间的向后兼容性。

可以将 lambda 表达式传递给任何通常接收单个抽象方法接口的旧函数。

要接收 lambda 表达式,方法的参数类型应该是函数式接口。

JAVA Stream杂谈(一): 为什么要使用StreamApi

JAVA Stream杂谈(二): 函数式编程的概念

文章分类
后端
文章标签