目录

  • 一、Java的旅程
  • 二、Java 8 的主要特性
  • 三、Java8的特效一:函数式编程
  • 1.Lambda演算
  • 2.为什么我们需要 Java 中的函数式编程?
  • 3.什么是Catch?
  • 4.函数式编程的关键概念:
  • (1)函数
  • (2)高阶函数
  • (3)一阶函数
  • (4)一等函数
  • (5)Monad


一、Java的旅程

让我们从头开始,看看Java的旅程。

java t 范型 java编程范式_函数式


注意:上面提到的 Java 11 特性不是 Java 11 版本中包含的最终特性。此列表仅提及最有可能包含或删除的功能。另请注意,Java 10 中列出的实验性 JIT 编译器 Graal 已经添加到 Java 9 中,但当时无法通过 JVM 参数启用它。

二、Java 8 的主要特性

继续前进,让我们看看 Java 8 中的关键特性:

java t 范型 java编程范式_java_02

三、Java8的特效一:函数式编程

Functional Programming(函数式编程):

Java 8 的主要部分旨在支持函数式编程。让我们探索和理解函数式编程的含义以及它如何有用以及如何在 Java 中应用。

函数式编程是一种编程范式,它规定了一种不同的算法方式来思考问题并为其编程解决方案。将其与面向对象编程进行对比,在 OOP 中,主要抽象是类/对象,而在函数式编程中,主要抽象是函数。在 OOP 中,对象构成了计算的构建块。同样,在函数式编程中,函数构成了计算的构建块。

“在函数式编程中,所有计算都是通过执行(数学)函数来进行的。” 这些(数学)函数具有不改变任何状态且仅对输入进行操作的特性,即对它们的执行没有副作用。

函数式编程强加了声明式编程风格,即我们通过表达式或声明进行编程(计算以函数/表达式——黑盒子的形式表达)。声明式编程风格是关于定义要做什么而不是如何做,将其与命令式编程进行对比,后者是关于定义要做什么以及如何做。简单来说,在声明式编程中,就是拥有一个抽象层,从而产生富有表现力的代码。函数式编程往往是声明式的,声明式的思考和编码往往会产生更好的、可读的和富有表现力的代码。声明式与命令式风格的一个例子是:

public class StringFinder {
  public static boolean findImperative(List<String> strings, String string) {
    boolean found = false;

    for (String str : strings) {
      if (str.equals(string)) {
        found = true;
        break;
      }
    }

    return found;
  }

  public static boolean findDeclarative(List<String> strings, String string) {
    return strings.contains(string);
  }
}

1.Lambda演算

函数式编程源于数学,并从其数学对应物——Lambda演算中汲取了大部分概念 。在某种意义上,编程语言中使用的所有函数式编程结构都是 lambda 演算的实现/阐述。让我们暂时转向数学。

Lambda 演算(λ-演算)是数学逻辑中的一个形式系统,用于表达基于函数抽象和使用变量绑定和替换的应用的计算。λ 演算为计算提供了简单的语义,使计算的性质能够被正式研究。该λ演算集成了两个简化,第一个简化的是,λ演算对待函数“匿名”,而不让他们有明确的名称,第二是,λ演算仅使用一个单一的输入的功能。需要两个或更多输入的普通函数需要柯里化。

简化的 Lambda 表达式语法:λvar.expr|var|(expr) . 这里, λvar是带有 am 输入变量的 lambda 声明var,.expr|var是函数定义/主体,(expr)是函数应用程序(或更简单的函数调用)。

例如:λx.x2(7)结果是 49。如果在 Java 或其他语言中将其表示为 lambda,则为x→x^2

在 lambda 演算中,函数是“一等”公民, 即函数可以用作输入或作为其他函数的输出返回。稍后将更详细地讨论一等函数。

对我来说另一个有趣的事情是 Church 还定义了 Church 编码方案,它可以表示 lambda 演算中的数据和运算符。自然数可以在 Church 编码方案下通过 lambda 演算表示,这些表示被称为Church numeral 。Church numeral n 是一个高阶函数,它以函数 f 作为参数,返回 f 的第 n 个组合,即函数 f 与自身组合 n 次。例如:数字3 := λf.λx.f (f (f x))

2.为什么我们需要 Java 中的函数式编程?

函数式编程的最大优点之一是,由于计算/程序是通过(数学)函数表达的,这些函数不会改变状态或有任何副作用,并且每个输入具有相同的输出, 这自然适合并发和并行适用性。随着我们朝着更多内核和分布式/并行计算的方向发展,事实证明 FP(函数式编程) 更适合这些要求。这就是为什么使用函数式语言或函数式构造来编写 AI/ML 或大数据解决方案的原因之一。例如:Apache Spark 是一个用 Scala 编码的大数据平台,它是一种(不纯粹的)函数式语言。另一个例子是 R,这是数据科学家中最流行的语言,它是函数式的。

遵循函数式风格往往会导致声明式代码 更加简洁和可读。

此外,由于代码仅作为函数编写,而不考虑状态、同步和其他问题,因此人们可以更多地关注问题而不是代码及其结构本身。

3.什么是Catch?

另一方面,没有副作用或不能改变状态并不都是好的,这是一个严重的限制,它限制了轻松有效地编写执行 IO 的系统的能力。像 Haskell 这样的函数式语言倾向于通过区分纯函数(数学函数)和不纯函数(不遵循数学函数的规则,即它们可能会改变状态,对于特定输入具有不同的输出,打印输出等)。存在于任何其他语言中的大多数 IO 操作都存在,但它们与语言的功能核心模块保持分离。

另一方面,我们需要评估 Java 并问自己为什么我们甚至会转向函数式编程。限制我们的差距是什么?

让我们暂时转向 OOP,看看它强加了什么限制。

众所周知,在面向对象的编程中,主要单元是一个对象(由类定义),它封装了对象的状态和行为。由于一切都是对象,要对系统或解决方案进行建模,就必须始终基于对象思考和提供实现。如果在某些情况下我们只需要实现一些行为/操作而不需要实现状态,OOP 会限制将该行为包装在一个类中以便能够执行它。这会导致太多的样板和冗长的代码,其中计算只需要执行一个函数。这种场景在函数式编程中处理起来相当简单和自然。

例如,一些这样的情况会实现比较逻辑(不可重用)并执行它,编写一些应该在单独的线程中执行一次的代码等等。可能有很多这样的情况,每当我们想要执行一个代码块时,我们都需要不必要地将它包装在一个类中。

Java 团队很早就发现了这个问题,并在 JDK 1.1 中添加了匿名内部类的支持,这样这个问题就可以用当时很容易支持的东西来修复一点。

所以这个“为什么?”的答案 很简单,正如所讨论的,两种范式都有其优点和缺点,因此 Java 选择了两全其美。它选择遵循混合编程范式来支持面向对象和函数式风格的编程,甚至以消除我们之前讨论过的缺点的方式将它们组合起来。

此外,该决定的另一个推动力是其开发人员基本不会转移或切换到其他平台或语言来利用函数式编程的优势。这样,与市场上的其他编程语言和平台相比,该语言保持了竞争力和生产力。

总体目标是能够以简洁自然的方式编写简单的函数/操作(仅仅是计算块),而不受编程语言或编程平台的约束。

4.函数式编程的关键概念:

让我们来看看函数式编程中的关键概念,以便我们可以将 Java 8 中可用的构造联系起来以支持它。

(1)函数

在数学中,函数是输入和输出之间的映射。可以将其视为将输入转换为输出的“黑匣子”。我们需要观察函数的某些关键特征:

(1)函数避免改变状态和改变数据。他们观察到的所有状态只是提供给他们的输入
(2)函数不会改变输入的值。
(3)对于每个输入,都有一个特定的输出。例如,对于 f(x) == f(x),输入x的函数f的结果总是相同的。

java t 范型 java编程范式_Java_03

(2)高阶函数

将一个或多个函数作为参数and/or返回一个函数作为其结果的函数。

java t 范型 java编程范式_Java_04

(3)一阶函数

除高阶函数外的所有函数都称为一阶函数。

(4)一等函数

当函数被视为主要单元并且可以独立存在时,就称一种语言或系统具有一等函数。

java t 范型 java编程范式_java_05

(5)Monad

Monads 是一种表示定义为步骤序列的计算的结构。表示 monad 结构的类型(Java 意义上的类)定义了操作的链接和排序、基于操作及其序列的特征派生,以及将该类型与其他类似类型链接的机制。

java t 范型 java编程范式_函数式_06