V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
amy815
V2EX  ›  问与答

Java 中的函数式编程(一)概念

  •  
  •   amy815 · 2021-10-18 21:31:13 +08:00 · 659 次点击
    这是一个创建于 920 天前的主题,其中的信息可能已经有所发展或是发生改变。

    写在前面

    从 Java 8 开始,Java 语言添加了 lambda 表达式以及函数式接口等新特性。这意味着 Java 语言也开始逐步提供函数式编程的能力。

    事实上,如果你熟悉 Erlang 、Scala 、JavaScript 或 Python,那你或多或少对函数式编程相对熟悉。但如果你是一个通过常规路径学习的 Javaer,可能对函数式编程思想不甚了解,相对的,你可能对面向对象编程思想会更熟悉。

    先熟悉一下几个术语,有利于提升大家的逼格:
    FP,Functional Programming,函数式编程
    OOP,Object Oriented Programming,面向对象编程
    虽然 FP 是在最近 10 年才流行起来的,但它的历史和 OOP 几乎等长。

    本系列文章的重点在于介绍 Java 中的函数式编程,Java 作为一个经典的 OOP 编程语言,在实际应用中,大部分 Java 程序都是 OOP+FP 的混合式代码。因此,对于函数式编程中的一些高级特性和技巧,例如 Currying 、惰性求值、尾递归等,我们不做专门的阐述,感兴趣的同学,可以搜索公众号,员说,查看完整文章,一起讨论。

    下面,我们先了解一下函数式编程的定义以及它的优点。

    本文的示例代码可从 gitee 上获取:
    https://gitee.com/cnmemset/javafp

    什么是函数式编程?

    函数式编程是一种编程范式( programming paradigm ),追求的目标是整个程序都由函数调用( function applying )以及函数组合( function composing )构成的。

    函数调用大家容易理解,但在函数式编程中,函数调用有一个限制——它不会改变函数以外的其它状态,换而言之,即函数调用不会改变在该函数之外定义的变量值。这种函数有个专门的术语——纯函数( purely function )。纯函数有个特点,当参数值不变的时候,多次运行纯函数,得到的结果总是一样的。这个特点特别有利于对纯函数进行 unit test 和 debugging 。

    函数组合指的是将一系列简单函数组合起来形成一个复合函数。函数组合是一个相对复杂的概念,譬如在 Python 中:

    from functools import reduce
    def compose(*funcs) -> int:
        """将一组简单函数 [f, g, h] 组合为一个复合函数 (f(g(h(...)))) """
        return reduce(lambda f, g: lambda x: f(g(x)), funcs)

    # 例子
    f = lambda x: x + 1
    g = lambda x: x * 2
    h = lambda x: x - 3
    # 调用复合函数 f(g(h(x)):[(x-3) * 2] + 1
    print(compose(f, g, h)(10))  // print 15

    在 Java 中,java.util.Objects.Consumer<t style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;"> 接口的默认方法 andThen 是一个简单的函数组合函数:</t>

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

    函数式编程的特性
    函数式编程有几个重要的特性:

    1. 函数是“第一等公民”( first-class citizens )

    函数是“第一等公民”,意味着函数和其它数据类型具备同等的地位——可以赋值给某个变量,可以作为另一个函数的参数,也可以作为另一个函数的返回值。

    判断某种开发语言对函数式编程支持程度高低,一个重要的标准就是该语言是否把函数作为“第一等公民”。

    例如下面的 Java 代码,print 变量可以看做是一个匿名函数,它作为一个参数传入了函数 ArrayList.forEach 。更多的语言细节可以参考随后的系列文章。

    public static void simpleFunctinoProgramming() {
        List<String> l = Arrays.asList("a""b""c");
        Consumer<String> print = s -> System.out.println(s);
        l.forEach(print);
    }

    上述代码会输出:

    a
    b
    c

    没有“副作用( side effects )”

    “副作用( side effects )”,指的是函数在执行的时候,除了得出计算结果之外,还会改变函数以外的状态。“副作用”的典型场景就是修改了程序的全局变量(譬如 Java 中某个全局可见的类的属性值、某个类的静态变量等等);修改传入的参数也属于“副作用”之一; IO 操作或调用其它有“副作用”的函数也属于“副作用”。

    函数式编程中要求函数都是“纯函数( purely function )”。给定了参数后,多次运行纯函数,总会得到相同的返回值,而且它不会修改函数以外的状态或产生其它的“副作用”。

    “副作用”的含义是如此苛刻,但有的时候我们需要在计算过程中保存状态,然而我们又不能使用可变量,此时我们使用递归,利用保存在栈上的参数来记录状态。下面的代码是一个经典的实例,它定义了一个将字符串反转的函数 reverse 。可以看到,reverse 在执行时的中间状态,是通过它在递归时的参数来保存的。务必牢记,在函数式编程中,所有的参数和变量都是 final 的(只能赋值 1 次而且赋值后不可变):

    public static String reverse(final String arg) {
        if (arg.length() == 0) {
            return arg;
        } else 
    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3168 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 10:52 · PVG 18:52 · LAX 03:52 · JFK 06:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.