函数式 Java 实例 |第 1 部分——从命令式到声明式

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(443)   2024-03-29 16:21:48

函数式编程 (FP) 是关于避免重新分配变量、避免可变数据结构、避免状态并始终支持函数。如果我们将函数式技术应用于日常 Java 代码,我们可以从 FP 中学到什么?

在这个名为“Functional Java by Example”的系列中,我将分 8 期重构一段现有代码,看看我是否可以在 Java 中实现 Functional Nirvana

我在“真正的”函数式语言(如 Haskell 或 F#)方面没有太多经验,但我希望在每篇文章中通过示例来演示将其中一些实践应用于日常 Java 代码意味着什么。

希望最后你已经获得了一些洞察力并知道选择一些有利于你自己的代码库的技术。

这些是所有部分:

  • 第 1 部分 – 从命令式到声明式
  • 第 2 部分 – 命名事物
  • 第 3 部分 – 不要使用异常来控制流程
  • 第 4 部分 – 更喜欢不变性
  • 第 5 部分 – 将 I/O 移至外部
  • 第 6 部分 - 作为参数的函数
  • 第 7 部分 – 也将失败视为数据
  • 第 8 部分 – 更多纯函数

我将在每篇文章发表时更新链接。如果您通过内容联合阅读本文,请查看我的博客 上的原始文章。

每次也将代码推送到此 GitHub 项目。

免责声明:代码是用 Apache Groovy 编写的,主要是为了简洁,所以我不必在不需要的地方输入内容(你知道:输入)重要的例子。其次,这种语言让我开心。

为什么要关心函数式编程 (FP)?

如果您不是在Groovy实时流数据事件处理框架上使用 Haskell、F# 或 Scala,您不妨收拾行李。这些天甚至 JavaScript 人员都在围绕您的方法旋转函数——而且这种语言已经存在了一段时间。

有很多文章和视频让您相信,如果您现在跟上函数式的潮流,您就会被旧的 OOP 装置抛在后面,坦率地说,几年内就过时了。

好吧,我在这里告诉你这完全正确,但是 FP 确实有一些前提,例如可读性、可测试性和可维护性,我们也在我们的(企业)Java 代码中努力实现的价值观对吗?

当您阅读本文时,多年来您可能已经直言不讳地认为 FP 是向前或向后或 anno 2017-2018 的一步,您只是乐于接受新想法

您可以通过学习 FP 来提高您的每种语言的技能。

自己确定可以从中学到什么,以及您自己的编程如何从中受益。

如果你能胜任这项任务,让我们从这个系列开始……

一些现有代码

关于示例代码的一句话:为像这样的博客想出人为的示例是相当棘手的:它应该足够容易以吸引广大受众,足够简单以便无需太多内容就可以理解情境,但仍然足够有趣以产生预期的学习效果。

展望未来,本系列的每一期都将建立在前一期的基础上。下面是我们将作为起点的代码。

所以,戴上你的眼镜,看看你是否熟悉下面的编码风格。

class FeedHandler {

  Webservice webservice
  DocumentDb documentDb

  void handle(List<Doc> changes) {

    for (int i = 0; i < changes.size(); i++) {
      def doc = changes[i]
      if (doc.type == 'important') {

        try {
          def resource = webservice.create(doc)
          doc.apiId = resource.id
          doc.status = 'processed'
        } catch (e) {
          doc.status = 'failed'
          doc.error = e.message
        }
        documentDb.update(doc)
      }
    }
  }
}
  • 它是某种FeedHandler
  • 它有两个属性,一个是Webservice类,一个是DocumentDb类。
  • 有一个 handle 方法可以处理 Doc 对象列表。文件?

试着弄清楚这里发生了什么

..

..

..

完毕?

读这样的东西有时会让你觉得自己像个人类解析器。

扫描类名 (FeedHandler?) 和一个方法 (void handle) 可以让您在有些眼睛酸痛的情况下,对一切的目的有一种“感觉”。

但是,弄清楚 handle 方法中到底“处理”了什么要困难得多。

  • 那里有一个 for-loop — 但究竟迭代了什么?多少次?
  • 这个变量 webservice 被调用,返回一个叫做 resource 的东西。
  • 如果 webservice 成功返回,被迭代的 doc(文档?)会更新状态。
  • 似乎 webservice 也可以抛出一个 Exception,它被捕获并且文档被更新为另一个状态。
  • 最终,文档被这个 documentDb 实例“更新”。看起来像一个数据库。
  • 哦等等,这只发生在“重要”文档上 — 在执行上述所有操作之前首先检查 doc.type

或许,你听说过这样一句话:

阅读代码的次数多于编写代码的次数。

看看这片美丽:

for (int i = 0; i < changes.size(); i++) {

上面的代码是以命令式 风格编写的,这意味着具体的语句——操纵状态和行为——是明确写出来的。

  • 用零初始化一个int i
  • int i 小于 changes 列表的大小时循环
  • 每次迭代将 int i 加 1

在这种命令式(过程式)编码风格中(大多数主流语言,包括面向对象编程 (OOP) 语言,如 Java、C++、C# 的设计主要支持这种风格),开发人员编写计算机完成特定任务所需执行的确切语句。

一些非常命令式(过程性)代码的信号:

  1. 关注如何执行任务
  2. 状态变化和执行顺序很重要
  3. 许多循环和条件

代码明确关注“如何”——这使得“什么”难以确定。

专注于什么

我们的第一步,正如本文的标题已经提到的那样,是摆脱命令式编码风格并重构为更具声明性风格——FP是表单。

循环 最让我烦恼。

这是新版本的代码。

class FeedHandler {

  Webservice webservice
  DocumentDb documentDb

  void handle(List<Doc> changes) {

    // for (int i = 0; i < changes.size(); i++) {
    //    def doc = changes[i]
    changes
      .findAll { doc -> doc.type == 'important' }
      .each { doc ->

      try {
        def resource = webservice.create(doc)
        doc.apiId = resource.id
        doc.status = 'processed'
      } catch (e) {
        doc.status = 'failed'
        doc.error = e.message
      }
      documentDb.update(doc)
    }
  }
}

有什么变化?

  • if (doc.type == 'important') 部分在文档集合本身上再次被替换为 findAll { doc -&gt; doc.type == 'important' }意思是“找到所有重要的文档并返回一个新集合只有那些重要文件”
  • 命令式 for-loop(带有中间 i 变量)已被文档集合本身的声明式 each 方法所取代 — 意思是“为列表中的每个文档执行一段代码,我不在乎你是怎么做的” ��

不用担心 eachfindAll:这些方法是由 Groovy 添加的,我很高兴在同一代码库中将它们与 Java 一起用于任何集合,例如设置、列表、Map。 Vanilla Java 8 具有等效的机制,例如 forEach 以更具声明性的方式迭代集合。

导致可读软件的是:

描述“什么”,而不是“如何”

如果我以更功能性的风格编写代码,我可以很容易地看到发生了什么,这节省了我的时间(因为是的,我确实阅读 90% 的时间是编写代码而不是编写代码)并且像这样编写代码不易出错,因为行数越少,错误隐藏的机会就越少。

这是现在

在第 2 部分中,我们将正确命名,为更多函数式编程铺平道路,例如本系列后面的“Either”或“Try”。

标签2: Java教程
地址:https://www.cundage.com/article/jcg-functional-java-example-part-1-imperative-declarative.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...