十三、函数式编程(1)

news2024/12/27 13:01:28

本章概要

  • 新旧对比
  • Lambda 表达式
    • 递归

函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程。

在计算机时代早期,内存是稀缺和昂贵的。几乎每个人都用汇编语言编程。人们虽然知道编译器,但编译器生成的代码很低效,比手工编码的汇编程序多很多字节,仅仅想到这一点,人们还是选择汇编语言。

通常,为了使程序能在有限的内存上运行,在程序运行时,程序员通过修改内存中的代码,使程序可以执行不同的操作,用这种方式来节省代码空间。这种技术被称为自修改代码 (self-modifying code)。只要程序小到几个人就能够维护所有棘手和难懂的汇编代码,你就能让程序运行起来。

随着内存和处理器变得更便宜、更快。C 语言出现并被大多数汇编程序员认为更“高级”。人们发现使用 C 可以显著提高生产力。同时,使用 C 创建自修改代码仍然不难。

随着硬件越来越便宜,程序的规模和复杂性都在增长。这一切只是让程序工作变得困难。我们想方设法使代码更加一致和易懂。使用纯粹的自修改代码造成的结果就是:我们很难确定程序在做什么。它也难以测试:除非你想一点点测试输出,代码转换和修改等等过程?

然而,使用代码以某种方式操纵其他代码的想法也很有趣,只要能保证它更安全。从代码创建,维护和可靠性的角度来看,这个想法非常吸引人。我们不用从头开始编写大量代码,而是从易于理解、充分测试及可靠的现有小块开始,最后将它们组合在一起以创建新代码。难道这不会让我们更有效率,同时创造更健壮的代码吗?

这就是函数式编程(FP)的意义所在。通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,函数式语言已经产生了优雅的语法,这些语法对于非函数式语言也适用。

你也可以这样想:

OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。

纯粹的函数式语言在安全性方面更进一步。它强加了额外的约束,即所有数据必须是不可变的:设置一次,永不改变。将值传递给函数,该函数然后生成新值但从不修改自身外部的任何东西(包括其参数或该函数范围之外的元素)。当强制执行此操作时,你知道任何错误都不是由所谓的副作用引起的,因为该函数仅创建并返回结果,而不是其他任何错误。

更好的是,“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一(当程序的某些部分同时在多个处理器上运行时)。这是可变共享状态的问题,这意味着代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存(谁赢了?没人知道)。如果函数永远不会修改现有值但只生成新值,则不会对内存产生争用,这是纯函数式语言的定义。 因此,经常提出纯函数式语言作为并行编程的解决方案(还有其他可行的解决方案)。

需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:为“并行编程”,“代码可靠性”和“代码创建和库复用”。 关于函数式编程能高效创建更健壮的代码这一观点仍存在部分争议。虽然已有一些好的范例,但还不足以证明纯函数式语言就是解决编程问题的最佳方法。

FP 思想值得融入非 FP 语言,如 Python。Java 8 也从中吸收并支持了 FP。我们将在此章探讨。

新旧对比

通常,传递给方法的数据不同,结果不同。如果我们希望方法在调用时行为不同,该怎么做呢?结论是:只要能将代码传递给方法,我们就可以控制它的行为。此前,我们通过在方法中创建包含所需行为的对象,然后将该对象传递给我们想要控制的方法来完成此操作。下面我们用传统形式和 Java 8 的方法引用、Lambda 表达式分别演示。代码示例:

interface Strategy {
    String approach(String msg);
}

class Soft implements Strategy {
    @Override
    public String approach(String msg) {
        return msg.toLowerCase() + "?";
    }
}

class Unrelated {
    static String twice(String msg) {
        return msg + " " + msg;
    }
}

public class Strategize {
    Strategy strategy;
    String msg;

    Strategize(String msg) {
        strategy = new Soft(); // [1]
        this.msg = msg;
    }

    void communicate() {
        System.out.println(strategy.approach(msg));
    }

    void changeStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public static void main(String[] args) {
        Strategy[] strategies = {
                new Strategy() { // [2]
                    @Override
                    public String approach(String msg) {
                        return msg.toUpperCase() + "!";
                    }
                },
                msg -> msg.substring(0, 5), // [3]
                Unrelated::twice // [4]
        };
        Strategize s = new Strategize("Hello there");
        s.communicate();
        for (Strategy newStrategy : strategies) {
            s.changeStrategy(newStrategy); // [5]
            s.communicate(); // [6]
        }
    }
}

输出结果:

在这里插入图片描述

Strategy 接口提供了单一的 approach() 方法来承载函数式功能。通过创建不同的 Strategy 对象,我们可以创建不同的行为。

我们一般通过创建一个实现Strategy接口的类来实现这种行为,正如在Soft里所做的。

  • [1]Strategize 中,你可以看到 Soft 作为默认策略,在构造函数中赋值。
  • [2] 一种较为简洁且更加自然的方法是创建一个匿名内部类。即便如此,仍有相当数量的冗余代码。你总需要仔细观察后才会发现:“哦,我明白了,原来这里使用了匿名内部类。”
  • [3] Java 8 的 Lambda 表达式,其参数和函数体被箭头 -> 分隔开。箭头右侧是从 Lambda 返回的表达式。它与单独定义类和采用匿名内部类是等价的,但代码少得多。
  • [4] Java 8 的方法引用,它以 :: 为特征。 :: 的左边是类或对象的名称, :: 的右边是方法的名称,但是没有参数列表。
  • [5] 在使用默认的 Soft 策略之后,我们逐步遍历数组中的所有 Strategy,并通过调用 changeStrategy() 方法将每个 Strategy 传入变量 s 中。
  • [6] 现在,每次调用 communicate() 都会产生不同的行为,具体取决于此刻正在使用的策略代码对象。我们传递的是行为,而并不仅仅是数据。

在 Java 8 之前,我们能够通过 [1][2] 的方式传递功能。然而,这种语法的读写非常笨拙,并且我们别无选择。方法引用和 Lambda 表达式的出现让我们可以在需要时传递功能,而不是仅在必要时才这么做。

Lambda表达式

Lambda 表达式是使用最小可能语法编写的函数定义:

  1. Lambda 表达式产生函数,而不是类。 虽然在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是类,但是幕后有各种操作执行让 Lambda 看起来像函数 —— 作为程序员,你可以高兴地假装它们“就是函数”。
  2. Lambda 语法尽可能少,这正是为了使 Lambda 易于编写和使用。

我们在 Strategize.java 中看到了一个 Lambda 表达式,但还有其他语法变体:

interface Description {
    String brief();
}

interface Body {
    String detailed(String head);
}

interface Multi {
    String twoArg(String head, Double d);
}

public class LambdaExpressions {

    static Body bod = h -> h + " No Parens!"; // [1]

    static Body bod2 = (h) -> h + " More details"; // [2]

    static Description desc = () -> "Short info"; // [3]

    static Multi mult = (h, n) -> h + n; // [4]

    static Description moreLines = () -> { // [5]
        System.out.println("moreLines()");
        return "from moreLines()";
    };

    public static void main(String[] args) {
        System.out.println(bod.detailed("Oh!"));
        System.out.println(bod2.detailed("Hi!"));
        System.out.println(desc.brief());
        System.out.println(mult.twoArg("Pi! ", 3.14159));
        System.out.println(moreLines.brief());
    }
}

输出结果:

在这里插入图片描述

我们从三个接口开始,每个接口都有一个单独的方法(很快就会理解它的重要性)。但是,每个方法都有不同数量的参数,以便演示 Lambda 表达式语法。

任何 Lambda 表达式的基本语法是:

  1. 参数。
  2. 接着 ->,可视为“产出”。
  3. -> 之后的内容都是方法体。
  • [1] 当只用一个参数,可以不需要括号 ()。 然而,这是一个特例。
  • [2] 正常情况使用括号 () 包裹参数。 为了保持一致性,也可以使用括号 () 包裹单个参数,虽然这种情况并不常见。
  • [3] 如果没有参数,则必须使用括号 () 表示空参数列表。
  • [4] 对于多个参数,将参数列表放在括号 () 中。

到目前为止,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 return 关键字是非法的。 这是 Lambda 表达式简化相应语法的另一种方式。

[5] 如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 return

Lambda 表达式通常比匿名内部类产生更易读的代码,因此我们将在本书中尽可能使用它们。

递归

递归函数是一个自我调用的函数。可以编写递归的 Lambda 表达式,但需要注意:递归方法必须是实例变量或静态变量,否则会出现编译时错误。 我们将为每个案例创建一个示例。

这两个示例都需要一个接受 int 型参数并生成 int 的接口:

interface IntCall {
    int call(int arg);
}

整数 n 的阶乘将所有小于或等于 n 的正整数相乘。 阶乘函数是一个常见的递归示例:

public class RecursiveFactorial {
    static IntCall fact;

    public static void main(String[] args) {
        fact = n -> n == 0 ? 1 : n * fact.call(n - 1);
        for (int i = 0; i <= 10; i++) {
            System.out.println(fact.call(i));
        }
    }
}

输出结果:

在这里插入图片描述

这里,fact 是一个静态变量。 注意使用三元 if-else。 递归函数将一直调用自己,直到 i == 0。所有递归函数都有“停止条件”,否则将无限递归并产生异常。

我们可以将 Fibonacci 序列用递归的 Lambda 表达式来实现,这次使用实例变量:

public class RecursiveFibonacci {
    IntCall fib;

    RecursiveFibonacci() {
        fib = n -> n == 0 ? 0 :
                n == 1 ? 1 :
                        fib.call(n - 1) + fib.call(n - 2);
    }

    int fibonacci(int n) {
        return fib.call(n);
    }

    public static void main(String[] args) {
        RecursiveFibonacci rf = new RecursiveFibonacci();
        for (int i = 0; i <= 10; i++) {
            System.out.println(rf.fibonacci(i));
        }
    }
}

输出结果:

在这里插入图片描述

Fibonacci 序列中的最后两个元素求和来产生下一个元素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995240.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手写Spring:第20章-事务处理

文章目录 一、目标&#xff1a;事务处理二、设计&#xff1a;事务处理2.1 事务单元测试2.2 事务设计 三、实现&#xff1a;事务处理3.1 工程结构3.2 事务管理的核心类图3.3 定义事务注解3.3.1 定义事务注解3.3.2 定义事务接口3.3.3 默认事务定义实现类3.3.4 委托事务定义实现类…

Java 多线程系列Ⅵ(并发编程的六大组件)

JUC 组件 前言一、Callable二、ReentrantLock三、Atomic 原子类四、线程池五、Semaphore六、CountDownLatch 前言 JUC&#xff08;Java.util.concurrent&#xff09;是 Java 标准库中的一个包&#xff0c;它提供了一组并发编程工具&#xff0c;本篇文章就介绍几组常见的 JUC 组…

win10自带wifi共享功能

1、按下【wini】组合键打开windows设置&#xff0c;点击【网络和internet】&#xff1b; 2、按照下图&#xff0c;打开个移动热点&#xff0c;设置名称、密码。

Blender--》页面布局及基本操作讲解

接下来我会在three.js专栏中分享关于3D建模知识的文章&#xff0c;如果学习three朋友并且想了解和学习3D建模&#xff0c;欢迎关注本专栏&#xff0c;关于这款3D建模软件blender的安装&#xff0c;我在前面的文章已经讲解过了&#xff0c;如果不了解的朋友可以去考考古&#xf…

DeepinV20安装MSJDK17

装什么版本的JDK https://learn.microsoft.com/zh-cn/java/openjdk/download#openjdk-17 通常来讲&#xff0c;选择最适应自己应用程序的版本&#xff0c;例如最新开发的程序基本需要运行在jdk17了&#xff0c;又或者前几年的java程序基本都是jdk11,再旧一点的jdk8。尽可能选…

【C++深入浅出】类和对象中篇(六种默认成员函数、运算符重载)

目录 一. 前言 二. 默认成员函数 三. 构造函数 3.1 概念 3.2 特性 四. 析构函数 4.1 概念 4.2 特性 五. 拷贝构造函数 5.1 概念 5.2 特性 六. 运算符重载 6.1 引入 6.2 概念 6.3 注意事项 6.4 重载示例 6.5 赋值运算符重载 6.6 前置和后置运算符重载 七. c…

【Rust日报】2023-09-07 Servo 项目将加入欧洲 Linux 基金会

Servo 项目将加入欧洲 Linux 基金会 Servo 项目由 Mozilla Research 于 2012 年创建&#xff0c;是除编译器本身之外的首个主要 Rust 代码库&#xff0c;自此成为实验性网络引擎设计的标志。Servo 的主要组件已被集成到 Firefox 网络浏览器中&#xff0c;其若干解析器和其他底层…

渗透测试基础之永恒之蓝漏洞复现

渗透测试MS17-010(永恒之蓝)的漏洞复现 目录 渗透测试MS17-010(永恒之蓝)的漏洞复现 目录 前言 思维导图 1,渗透测试 1,1,什么是渗透测试? 1.2,渗透测试的分类: 1.3,渗透测试的流程 1.3.1,前期交互 1.3.2,情报收集 1.3.3,威胁建模 1.3.4,漏洞分析 1.3.5,漏洞验…

软件设计模式(五):代理模式

前言 代理模式是软件设计模式的重中之重&#xff0c;代理模式在实际应用比较多&#xff0c;比如Spring框架中的AOP。在这篇文章中荔枝将会梳理有关静态代理、动态代理的区别以及两种实现动态代理模式的方式。希望能对有需要的小伙伴有帮助~~~ 文章目录 前言 一、静态代理 二…

自定义Dynamics 365实施和发布业务解决方案 - 1. 准备工作

在当前的商业世界中,竞争每时每刻都在加剧每个企业都必须找到在竞争中保持领先的直观方法。其中之一企业面临的主要挑战是在以便为客户提供更好的服务。在这样一个竞争激烈、要求苛刻的时代环境中,对客户关系管理软件的需求是正在增加。 Dynamics 365的CE功能强大且适应性强…

使用JS实现一个简单的观察者模式(Observer)

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 手撸Observer⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领…

MySQL数据库——存储引擎(1)-MySQL体系结构、存储引擎简介

目录 MySQL体系结构 连接层 服务层 引擎层 存储层 存储引擎简介 概念 语句 演示 下面开始学习进阶篇的第一个内容——存储引擎 分为四点学习&#xff1a; MySQL体系结构存储引擎简介存储引擎特点存储引擎选择 MySQL体系结构 连接层 最上层是一些客户端和链接服务&am…

小米和金山集团董事长雷军访问武汉:加大投资力度,深化务实合作

小米集团创始人雷军一行在9月6日到访了武汉&#xff0c;受到了当地政府的热情欢迎。武汉方面表示&#xff0c;小米、金山集团作为全球知名的企业集团&#xff0c;与武汉有着良好合作基础。未来&#xff0c;武汉希望小米、金山集团持续深耕武汉&#xff0c;加大投资力度&#xf…

主页整理:8月1日---9月10日

目录 8月1日17点 8月1日20点 8月3日13点 8月3日18点 8月15日19点 8月28日9点 8月28日18点 8月29日8点 8月29日9点 9月2日21点 9月5日17点 9月9日18点 9月10日7点 粉丝变化数 8月1日17点 8月1日20点 8月3日13点 8月3日18点 8月15日19点 8月28日9点 8月28日18点…

Element-ui container常见布局

1、header\main布局 <template> <div> <el-container> <el-header>Header</el-header> <el-main>Main</el-main> </el-container> </div> </template> <style> .el-header { …

日常开发小汇总(3)js类型判断

1.typeof 能判断出字符串、数字、方法和undefined&#xff0c;array、null、object判断不出 let num 1;let str "x";let fn function user(){}let arr [1,2]let obj {name:"zhangs"}let und;let nul null;console.log(typeof num) //numberconsole.l…

深度、广度优先遍历(邻接表)

#include<stdio.h> #include<stdlib.h> #include<iostream> #include<queue> #define MAXVEX 20 typedef char VertexType; using namespace std;//边表结点 typedef struct EdgeNode{int adjvex;struct EdgeNode *next; }EdgeNode;//顶点结点 typedef…

Spring Cloud:构建微服务的最佳实践

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

手机木马远程控制复现

目录 目录 前言 系列文章列表 渗透测试基础之永恒之蓝漏洞复现http://t.csdn.cn/EsMu2 思维导图 1&#xff0c;实验涉及复现环境 2,Android模拟器环境配置 2.1,首先从官网上下载雷电模拟器 2.2,安装雷电模拟器 2.3, 对模拟器网络进行配置 2.3.1,为什么要进行配置…

vagrant 虚拟机扩容磁盘

vagrant 虚拟机扩容磁盘 修改配置安装插件存储扩容 修改配置 参考博客:https://blog.csdn.net/marina_1/article/details/122238721 vagrant 版本 PS D:\vagrant\workplace\node2> vagrant --version Vagrant 2.3.7修改vagrant虚拟机配置文件Vagrantfile&#xff0c;添加磁…