对于java大家都已经不陌生了吧,今天小猿圈Java讲师就分享一篇关于java函式式编码结构及优势的知识点,希望对于学习java的你有一定的帮助,想学习就需要积累。
探讨三种下一代JVM语言:Groovy、Scala和Clojure,比较并对比新的功能和范例,让Java开发人员对自己近期的未来发展有大体的认识。
当垃圾回收成为主流时,它消除了所有类别的难以除错的问题,使执行时能够为开发人员管理复杂的、容易出错的程序。函数语言程式设计旨在为您编写的算法实现同样的优化,这样您就可以从一个更高的抽象层面开展工作,同时执行时执行复杂的优化。
Java下一代语言并不都占用从命令式到函式式的语言频谱的同一位置,但都展现出函式功能和习语。函数语言程式设计技术有明确定义,但语言有时为相同的函式式概念使用不同的术语,使得我们很难看到相似之处。在本期文章中,我比较了Scala、Groovy和Clojure的函式式编码风格并讨论了它们的优势。
命令式处理
我要首先探讨一个常见问题及其命令式解决方案。假如给定一个名称列表,其中一些名称包含一个字元。系统会要求您在一个逗号分隔的字串中返回名称,该字串中不包含单字母的名称,每个名称的首字母都大写。实现该算法的Java程式码如清单1所示。
清单1.命令式处理
public class TheCompanyProcess {
public String cleanNames(List listOfNames) {
StringBuilder result = new StringBuilder;
for(int i = 0; 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 #(
Clojure的thread-first宏
thread-last宏使集合的处理变得更加简单。类似的Clojure宏thread-first可简化与JavaAPI的互动。例如普遍的Java程式码语句person.getInformation.
getAddress.getPostalCode,这体现了Java违反迪米特法则的倾向。这种型别的语句给Clojure程式设计带来一些烦恼,迫使使用JavaAPI的开发人员不得不构建由内而外的语句,比如(getPostalCode(getAddress(getInformationperson)))。thread-first宏消除了这一语法困扰。您可以使用宏将巢状呼叫编写为(->persongetInformationgetAddressgetPostalCode),想巢状多少层都可以。
如果您不习惯检视Clojure,可以使用清单4中的程式码,其结构可能不够清晰。Clojure这样的Lisp是“由内而外”进行工作的,所以必须从最后的引数值list-of-emps着手。Clojure的(filter)函式接受两个引数:用于进行筛选的函式(本例中为匿名函式)和要筛选的集合。
您可以为第一个引数编写一个正式函式定义,比如(fn[x](
最后,(map)操作的结果成为了(reduce)的集合引数。(reduce)的第一个引数是组合函式(应用于(interpose)的返回的(str))。(interpose)在集合的每个元素之间(除了最后一个)插入其第一个引数。
当函式巢状过多时,即使最有经验的开发人员也会倍感头疼,如清单4中的(process)函式所示。所幸的是,Clojure包含的宏支援您将结构“调整”为更可读的顺序。清单5中的功能与清单4中的功能一样。
清单5.使用Clojure的thread-last宏
(defn process2 [list-of-emps]
(->> list-of-emps
(filter #(
(map clojure.string/capitalize)
(interpose ",")
(reduce str)))
Clojurethread-last宏采取对集合应用各种变换的常见操作并颠倒典型的Lisp的顺序,恢复了从左到右的更自然的阅读方式。在清单5中,首先是(list-of-emps)集合。程式码块中每个随后的表单被应用于前一个表单。Lisp的优势之一在于其语法灵活性:任何时候程式码的可读性变得很差时,您都可以将程式码调整回具有较高可读性。
函数语言程式设计的优势
在一篇标题为“BeatingtheAverages”的著名文章中,PaulGraham定义了BlubParadox:他“编造”了一种名为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中的一种新编码风格并展示了一些优势。
小猿圈Java讲师提醒大家:每天学习一点技术问题,只要功夫深,铁杵磨成针,学习不是一朝一夕的,是需要付出行动的,而且还要坚持小猿圈java自学交流群:743849624,学习新的技术需要不断的查阅资料,看视讯,复习,练习,如果你工作中或者生活中遇到什么问题,可以到小猿圈去寻找答案的,相信会给你满意的答复的。





























