简单了解java函数式编码结构及优势

前言

当垃圾回收成为主流时,它消除了所有类别的难以调试的问题,使运行时能够为开发人员管理复杂的、容易出错的进程。函数式编程旨在为您编写的算法实现同样的优化,这样您就可以从一个更高的抽象层面开展工作,同时运行时执行复杂的优化。

Java 下一代语言并不都占用从命令式到函数式的语言频谱的同一位置,但都展现出函数功能和习语。函数式编程技术有明确定义,但语言有时为相同的函数式概念使用不同的术语,使得我们很难看到相似之处。在本期文章中,我比较了 Scala、Groovy 和 Clojure 的函数式编码风格并讨论了它们的优势。

命令式处理

我要首先探讨一个常见问题及其命令式解决方案。假如给定一个名称列表,其中一些名称包含一个字符。系统会要求您在一个逗号分隔的字符串中返回名称,该字符串中不包含单字母的名称,每个名称的首字母都大写。实现该算法的 Java 代码如清单 1 所示。

清单 1. 命令式处理

public class TheCompanyProcess {
public String cleanNames(List<String> listOfNames) {
StringBuilder result = new StringBuilder();
for(int i = 0; i < listOfNames.size(); i++) {
if (listOfNames.get(i).length() > 1) {
result.append(capitalizeString(listOfNames.get(i))).append(",");
}
}
return result.substring(0, result.length() - 1).toString();
}
public String capitalizeString(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1, s.length());
}
}

由于您必须处理整个列表,解决清单 1 中问题最简单的方式是使用一个命令式循环。对于每个名称,都需要进行检查,确认其长度是否大于 1,然后(如果长度大于 1)将首字母大写的名称附加到 result 字符串,并在后面加逗号。最终字符串中的最后一个名称不应包含逗号,所以我将它从最后返回值中移走。

在命令式编程中,建议您在较低级上别执行操作。在 清单 1 中的 cleanNames() 方法中,我执行了三个任务:我筛选 列表以消除单字符,将列表中每个名称的首字母变换 为大写,然后将列表转化 为一个字符串。在命令式语言中,我不得不为三个任务都使用同一低级机制(对列表进行迭代)。函数式语言将筛选、变换和转化视为常见操作,因此它们提供给您从不同视角解决问题的方式。

函数式处理

函数编程语言与命令式语言的问题分类方式不同。筛选、变换和转化逻辑类别表现为函数。那些函数实现低级变换并依赖于开发人员来编写作为参数传递的函数,进而定制函数的行为。我可以用伪代码将 清单 1 中的问题概念化为:

listOfEmps -> filter(x.length > 1) -> transform(x.capitalize) ->
convert(x, y -> x + "," + y)

利用函数式语言,您可以建模这一概念性解决方案,无需担心实现细节。

Scala 实现

清单 2 使用 Scala 实现 清单 1 中的处理示例。它看起来就像是前面的伪代码,包含必要的实现细节。

清单 2. Scala 处理

val employees = List("neal", "s", "stu", "j", "rich", "bob")
val result = employees
.filter(_.length() > 1)
.map(_.capitalize)
.reduce(_ + "," + _)

对于给定的名称列表,我首先筛选它,剔除长度不大于 1 的所有名称。然后将该操作的输出提供给 map() 函数,该函数对集合的每个元素执行所提供的代码块,返回变换后的集合。最后,来自 map() 的输出集合流向 reduce() 函数,该函数基于代码块中提供的规则将每个元素结合起来。

在本例中,我将每对元素结合起来,用插入的逗号连接它们。我不必考虑三个函数调用中参数的名称是什么,所以我可以使用方便的 Scala 快捷方式,也就是说,使用 _ 跳过名称。reduce() 函数从前两个元素入手,将它们结合成一个元素,成为下一个串接中的第一个元素。在 “浏览” 列表的同时,reduce() 构建了所需的逗号分隔的字符串。

我首先展示 Scala 实现是因为我对它的语法比较熟悉,而且 Scala 分别为筛选、变换和转化概念使用了行业通用的名称,即 filter、map 和 reduce。

Groovy 实现

Groovy 拥有相同的功能,但对它们进行命名的方式与脚本语言(比如 Ruby)更加一致。清单 1 中处理示例的 Groovy 版本如清单 3 所示。

清单 3. Groovy 处理

class TheCompanyProcess {
public static String cleanUpNames(List listOfNames) {
listOfNames
.findAll {it.length() > 1}
.collect {it.capitalize()}
.join(',')
}
}

尽管清单 3 在结构上类似于 清单 2 中的 Scala 示例,但方法名称不同。Groovy 的 findAll 集合方法应用所提供的代码块,保留代码块为 true 的元素。如同 Scala,Groovy 包含一个隐式参数机制,为单参数代码块使用预定义的 it 隐式参数。collect 方法(Groovy 的 map 版本)对集合的每个元素执行所提供的代码块。Groovy 提供一个函数 (join()),使用所提供的分隔符将字符串集合串联为单一字符串,这正是本示例中所需要的。

Clojure 实现

Clojure 是一个使用 reduce、map 和 filter 函数名的函数式语言,如清单 4 所示。

清单 4. Clojure 处理示例

(defn process [list-of-emps]
(reduce str (interpose ","
(map clojure.string/capitalize
(filter #(< 1 (count %)) list-of-emps)))))

Clojure 的 thread-first 宏

thread-last 宏 使集合的处理变得更加简单。类似的 Clojure 宏 thread-first 可简化与 Java API 的交互。例如普遍的 Java 代码语句 person.getInformation().

getAddress().getPostalCode(),这体现了 Java 违反 迪米特法则 的倾向。这种类型的语句给 Clojure 编程带来一些烦恼,迫使使用 Java API 的开发人员不得不构建由内而外的语句,比如 (getPostalCode (getAddress (getInformation person)))。thread-first 宏消除了这一语法困扰。您可以使用宏将嵌套调用编写为 (-> person getInformation getAddress getPostalCode),想嵌套多少层都可以。

如果您不习惯查看 Clojure,可以使用清单 4 中的代码,其结构可能不够清晰。Clojure 这样的 Lisp 是 “由内而外” 进行工作的,所以必须从最后的参数值 list-of-emps 着手。Clojure 的 (filter ) 函数接受两个参数:用于进行筛选的函数(本例中为匿名函数)和要筛选的集合。

您可以为第一个参数编写一个正式函数定义,比如 (fn [x] (< 1 (count x))),但使用 Clojure 可以更简洁地编写匿名函数。与前面的示例一样,筛选操作的结果是一个较少的集合。(map ) 函数将变换函数接受为第一个参数,将集合(本例中是 (filter ) 操作的返回值)作为第二个参数。Clojure 的 (map ) 函数的第一个参数通常是开发人员提供的函数,但接受单一参数的任何函数都有效;内置 capitalize 函数也符合要求。

最后,(map ) 操作的结果成为了 (reduce ) 的集合参数。(reduce ) 的第一个参数是组合函数(应用于 (interpose ) 的返回的 (str ))。(interpose ) 在集合的每个元素之间(除了最后一个)插入其第一个参数。

当函数嵌套过多时,即使最有经验的开发人员也会倍感头疼,如 清单 4 中的 (process ) 函数所示。所幸的是,Clojure 包含的宏支持您将结构 “调整” 为更可读的顺序。清单 5 中的功能与 清单 4 中的功能一样。

清单 5. 使用 Clojure 的 thread-last 宏

(defn process2 [list-of-emps]
(->> list-of-emps
(filter #(< 1 (count %)))
(map clojure.string/capitalize)
(interpose ",")
(reduce str)))

Clojure thread-last 宏采取对集合应用各种变换的常见操作并颠倒典型的 Lisp 的顺序,恢复了从左到右的更自然的阅读方式。在 清单 5 中,首先是 (list-of-emps) 集合。代码块中每个随后的表单被应用于前一个表单。Lisp 的优势之一在于其语法灵活性:任何时候代码的可读性变得很差时,您都可以将代码调整回具有较高可读性。

函数式编程的优势

在一篇标题为 “Beating the Averages” 的著名文章中,Paul Graham 定义了 Blub Paradox:他 “编造” 了一种名为 Blub 的虚假语言,并且考虑在其他语言与 Blub 之间进行功能比较:

只要我们假想的 Blub 程序员往下看一连串功能,他就知道自己是在往下看。不如 Blub 功能强大的语言显然不怎么强大,因为它们缺少程序员习惯使用的一些功能。但当我们假想的 Blub 程序员从另一个方向,也就是说,往上看一连串功能时,他并没有意识到自己在往上看。他看到的只不过是怪异的语言。他可能认为它们在功能上与 Blub 几近相同,只是多了其他难以理解的东西。Blub 对他而言已经足够好,因为他是在 Blub 环境中可以思考问题。

对于很多 Java 开发人员而言,清单 2 中的代码看起来陌生而又奇怪,因此难以将它看作是有优势的代码。但当您停止过于细化任务执行细节时,就释放了越来越智能的语言和运行时的潜能,从而做出了强大的改进。例如,JVM 的到来(解除了开发人员的内存管理困扰)为先进垃圾回收的创建开辟了全新的研发领域。使用命令式编码时,您深陷于迭代循环的细节,难以进行并行性等优化。从更高的层面思考操作(比如 filter、map 和 reduce)可将概念与实现分离开来,将并行性等修改从一项复杂、详细的任务转变为一个简单的 API 更改。

想一想如何将 清单 1 中的代码变为多线程代码。由于您密切参与了 for 循环期间发生的细节,所以您还必须处理烦人的并发代码。然后思考一下清单 6 所示的 Scala 并行版本。

清单 6. 实现进程并行性

val parallelResult = employees
.par
.filter(f => f.length() > 1)
.map(f => f.capitalize)
.reduce(_ + "," + _)

清单 2 与 清单 6 之间惟一的差别在于,将 .par 方法添加到了命令流中。.par 方法返回后续操作依据的集合的并行版本。由于我将对集合的操作指定为高阶概念,所以底层运行时可以自由地完成更多的工作。

面向命令式对象的开发人员往往会考虑使用重用类,因为他们的语言鼓励将类作为构建块。函数编程语言倾向于重用函数。函数式语言构建复杂的通用功能(比如 filter()、map() 和 reduce())并通过作为参数提供的函数来实现定制。在函数式语言中,将数据结构转换为列表和映射等标准集合是很寻常的事,因为它们接着就可以被强大的内置函数所操控。

例如,在 Java 环境中存在许多 XML 处理框架,每个框架都封装自己的私有版本的 XML 结构,并通过自己的方法交付它。在 Clojure 这样的语言中,XML 被转换为基于映射的标准数据结构,该结构对已经存在于语言中的强大的变换、约简和筛选操作开放。

结束语

所有现代语言都包含或添加了函数式编程结构,使函数式编程成为未来开发中不可或缺的一部分。Java 下一代语言都实现了强大的函数式功能,有时使用不同的名称和行为。在本期中,我介绍了 Scala、Groovy 和 Clojure 中的一种新编码风格并展示了一些优势。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • java.net.MalformedURLException异常的解决方法

    java.net.MalformedURLException at java.net.URL.<init>(URL.java:619) at java.net.URL.<init>(URL.java:482) at java.net.URL.<init>(URL.java:431) 代码中URL url = new URL(someUrl);这一行出现java.net.MalformedURLException异常 解决方法是,对someUrl中的参数名和参数值都URL

  • 学习不同 Java.net 语言中类似的函数结构

    前言 函数式编程语言包含多个系列的常见函数.但开发人员有时很难在语言之间进行切换,因为熟悉的函数具有不熟悉的名称.函数式语言倾向于基于函数范例来命名这些常见函数.从脚本背景衍生而来的语言倾向于使用更具描述性的名称(有时是多个名称,包含多个指向同一个函数的别名). 在本期文章中,我将继续探讨 3 种重要函数(过滤.映射和缩减)的实用性,展示来自每种 Java 下一代语言的实现细节.文中的讨论和示例旨在减轻 3 种语言对类似函数结构使用的不一致名称时可能引起的混淆. 过滤 在过滤 函数中,您可指定一

  • java.net.ConnectException: Connection refused问题解决办法

    Socket异常 客户端异常 java.net.ConnectException: Connection refused: connect. 该异常发生在客户端进行new Socket(ip, port)操作时,该异常发生的原因是或者具有ip地址的机器不能找到(也就是说从当前机器不存在到指定ip路由),或者是该ip存在,但找不到指定的端口进行监听.出现该问题,首先检查客户端的ip和port是否写错了,如果正确则从客户端ping一下服务器看是否能ping通,如果能ping通(服务服务器端把ping

  • Java客户端调用.NET的WebService实例

    项目需要去调用.NET的WebSrevice,本身是Java,研究了半天,终于有些头绪,记下来. 1,新建.NET WebService.只在原方法上加上一个string类型的参数str [WebMethod] public string HelloWorld(string str) { return "Hello World"; } 2,新建Java的WebService客户端,lib引入以下5个jar包(我是用idea生成的WebService客户端,会下载7个包,我试着删掉了lo

  • 不同Java泛型构造函数的详解

    1.概述 我们之前讨论过Java Generics的基础知识.在本文中,我们将了解Java中的通用构造函数. 泛型构造函数是至少需要有一个泛型类型参数的构造函数.我们将看到泛型构造函数并不都是在泛型类中出现的,而且并非所有泛型类中的构造函数都必须是泛型. 2.非泛型类 首先,先写一个简单的类:Entry,它不是泛型类: public class Entry { private String data; private int rank; } 在这个类中,我们将添加两个构造函数:一个带有两个参数的

  • Android 中出现java.net.BindException: bind failed: EADDRINUSE 问题解决办法

    Android 中出现java.net.BindException: bind failed: EADDRINUSE 问题解决办法 看下问题: try{ DatagramSocket udpSocket = new DatagramSocket(DEFAULT_PORT ); } catch (Exception e) { e.printStackTrace(); } //java.net.BindException: bind failed: EADDRINUSE (Address alrea

  • java利用java.net.URLConnection发送HTTP请求的方法详解

    一.前言 如何通过Java发送HTTP请求,通俗点讲,如何通过Java(模拟浏览器)发送HTTP请求. Java有原生的API可用于发送HTTP请求,即java.net.URL.java.net.URLConnection,这些API很好用.很常用,但不够简便: 所以,也流行有许多Java HTTP请求的framework,如,Apache的HttpClient. 目前项目主要用到Java原生的方式,所以,这里主要介绍此方式. 二.运用原生Java Api发送简单的Get请求.Post请求步骤

  • 简单了解java函数式编码结构及优势

    前言 当垃圾回收成为主流时,它消除了所有类别的难以调试的问题,使运行时能够为开发人员管理复杂的.容易出错的进程.函数式编程旨在为您编写的算法实现同样的优化,这样您就可以从一个更高的抽象层面开展工作,同时运行时执行复杂的优化. Java 下一代语言并不都占用从命令式到函数式的语言频谱的同一位置,但都展现出函数功能和习语.函数式编程技术有明确定义,但语言有时为相同的函数式概念使用不同的术语,使得我们很难看到相似之处.在本期文章中,我比较了 Scala.Groovy 和 Clojure 的函数式编码风

  • Java函数式编程(一):你好,Lambda表达式

    第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化. 我们每天的工作将会变成更简单方便,更富表现力.Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了.这些新特性引入Java后,我们可以写出更简洁,优雅,表达性更强,错误更少的代码.我们可以用更少的代码来实现各种策略和设计模式. 在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程.在使用这种全新的优雅的方式进行设计编码之前,我们先来看下它到底好在哪里. 改变了你的思考方式 命令式风格--

  • Java函数式开发 Optional空指针处理

    摘要 空闲时会抽空学习同在jvm上运行的Groovy和Scala,发现他们对null的处理比早期版本Java慎重很多.在Java8中,Optional为函数式编程的null处理给出了非常优雅的解决方案.本文将说明长久以来Java中对null的蹩脚处理,然后介绍使用Optional来实现Java函数式编程. 那些年困扰着我们的null 在Java江湖流传着这样一个传说:直到真正了解了空指针异常,才能算一名合格的Java开发人员.在我们逼格闪闪的java码字符生涯中,每天都会遇到各种null的处理,

  • Java 函数式编程要点总结

    目录 一.函数式概念 二.函数与方法 三.JDK函数基础 1.Lambda表达式 2.函数式接口 四.Optional类 1.Null判断 2.Optional应用 五.Stream流 六.源代码地址 一.函数式概念 函数式编程是一种结构化编程的范式,主要思想是把运算过程尽量写成系列嵌套的函数调用.函数编程的概念表述带有很抽象的感觉,可以基于案例看: public class Function01 {     public static void main(String[] args) {   

  • Java字符编码原理(动力节点Java学院整理)

    Java开发中,常常会遇到乱码的问题,一旦遇到这种问题,常常比较烦恼,大家都不愿意承认是自己的代码有问题.其实编码问题并没有那么神秘,那么不可捉摸,搞清Java的编码本质过程就真相大白了. 先看个图: 其实,编码问题存在两个方面:JVM之内和JVM之外. 1.Java文件编译后形成class 这里Java文件的编码可能有多种多样,但Java编译器会自动将这些编码按照Java文件的编码格式正确读取后产生class文件,这里的class文件编码是Unicode编码(具体说是UTF-16编码). 因此

  • Java字符编码简介_动力节点Java学院整理

    1. 概述 本文主要包括以下几个方面:编码基本知识,Java,系统软件,url,工具软件等. 在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687".注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示". 2. 编码基本知识 最早的编码是iso88

  • 简单了解Java垃圾回收器的种类

    在这篇教程中我们将学习几种现有的垃圾回收器.在Java中,垃圾回收是一个自动的进程可以替代程序员进行内存的分配与回收这些复杂的工作.这篇是垃圾回 收教程系列的第三篇,在前面的第2部分我们看到了在Java中垃圾回收是如何工作的,那是篇有意思的文章,我推荐你去看一下.第一部分介绍了Java的垃圾回收,主要有JVM体系结构,堆内存模型和一些Java术语. Java有四种类型的垃圾回收器: 串行垃圾回收器(Serial Garbage Collector) 并行垃圾回收器(Parallel Garbag

  • 简单介绍Java垃圾回收机制

    Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Java深受大家欢迎的众多特性之一,能够帮助程序员更好地编写Java程序. 这篇教程是系列第一部分.首先会解释基本的术语,比如JDK.JVM.JRE和HotSpotVM.接着会介绍JVM结构和Java堆内存结构.理解这些基础对于理解后面的垃圾回收知识很重要. Java关键术语 JavaAPI:一系列帮助开发者创建Java应用程序的封装好的库. Java开发工具包(JDK):一

  • Java函数式接口Supplier接口实例详解

    这篇文章主要介绍了Java函数式接口Supplier接口实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供. 下面是最简单的Supplier接口及使用示例. Supplier接口概述 // Supplier接口源码 @FunctionalInterface public interface Supplier<T>

  • 简单了解java ORM框架JOOQ

    这篇文章主要介绍了简单了解java ORM框架JOOQ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言 今天给大家介绍一个新的ORM框架->JOOQ,可能很多朋友还没有听说过这个框架,码农哥之前也是一直在使用Mybatis框架作为Java工程中的持久层访问框架,但是最近的一些项目采用JOOQ框架开发后,码农哥表示再也不想用Mybatis了! 为什么这么说呢?因为JOOQ在代码层面要比Mybatis简洁得多,而且性能也非常优异.相信大家都有过

随机推荐