groovy邮件功能

    技术2024-03-21  90

    关于本系列

    本系列旨在将您的观点重新定位为实用的心态,帮助您以新的方式看待常见问题并找到改善日常编码的方式。 它探讨了函数式编程的概念,允许在Java语言中进行函数式编程的框架,在JVM上运行的函数式编程语言以及语言设计的一些未来方向。 该系列面向那些了解Java及其抽象如何工作,但很少或没有使用功能语言经验的开发人员。

    在上一期中 ,我展示了Groovy的一些即用型功能,以及如何使用Groovy的原语来构建无限列表。 在本期中,我将继续探索函数式编程与Groovy的交集。

    Groovy是一种多范式语言:它支持面向对象,元编程和函数式编程样式,它们大多彼此正交 (请参见正交性侧栏)。 元编程允许您向语言及其核心库添加功能。 通过将元编程与函数式编程相结合,可以使自己的代码更具函数性,或者扩展第三方函数库,以使其在Groovy中更好地工作。 我将首先展示Groovy的ExpandoMetaClass如何工作以扩充类,然后如何使用此机制将Functional Java库(请参阅参考资料 )编织到Groovy中。

    通过ExpandoMetaClass打开类

    正交性

    正交的定义涵盖了多个学科,包括数学和计算机科学。 在数学上,两个相互成直角的向量是正交的,这意味着它们永不相交。 在计算机科学中,正交组件彼此之间没有任何影响(或副作用)。 例如,函数编程和元编程在Groovy中是正交的,因为它们不会互相干扰:使用元编程不会限制您使用函数构造,反之亦然。 它们是正交的事实并不意味着它们不能一起工作 ,而只是它们不会互相干扰 。

    Groovy的更强大功能之一是open class ,它具有重新打开现有类以增强或删除其功能的能力。 这与子类化不同,子类化是从现有类型派生出一种新类型。 打开类使您可以重新打开诸如String类的类,并向其添加新方法。 测试库大量​​使用此功能来通过验证方法扩展Object ,以便应用程序中的所有类现在都具有验证方法。

    Groovy中有两个开放类技术:类和ExpandoMetaClass (参见相关主题 )。 在这个例子中,任何一个都可以工作。 我选择ExpandoMetaClass是因为它在语法上更加简单。

    如果您一直在关注本系列,那么您将熟悉我长期运行的数字分类示例。 清单1中所示的Groovy中完整的Classifier使用了Groovy自己的功能构造:

    清单1. Groovy中的完整Classifier
    class Classifier { def static isFactor(number, potential) { number % potential == 0; } def static factorsOf(number) { (1..number).findAll { i -> isFactor(number, i) } } def static sumOfFactors(number) { factorsOf(number).inject(0, {i, j -> i + j}) } def static isPerfect(number) { sumOfFactors(number) == 2 * number } def static isAbundant(number) { sumOfFactors(number) > 2 * number } def static isDeficient(number) { sumOfFactors(number) < 2 * number } static def nextPerfectNumberFrom(n) { while (!isPerfect(++n)); n } }

    如果对在此版本中如何实现这些方法有任何疑问,可以参考以前的部分(尤其是“ 耦合和组成,第2部分 ”和“ Groovy中的功能部件,第1部分 ”)。 要使用此类的方法,我可以以“常规”功能方式调用这些方法: Classifier.isPerfect(7) 。 但是,通过使用元编程,我可以将这些方法直接“连接”到Integer类中,从而允许我“询问”它属于哪个类别。

    要将这些方法添加到Integer类中,我访问该类的metaClass属性(由Groovy为每个类预定义),如清单2所示:

    清单2.将分类添加到Integer
    Integer.metaClass.isPerfect = {-> Classifier.isPerfect(delegate) } Integer.metaClass.isAbundant = {-> Classifier.isAbundant(delegate) } Integer.metaClass.isDeficient = {-> Classifier.isDeficient(delegate) }
    初始化元编程方法

    您必须在第一次尝试调用它们之前添加元编程方法。 对其进行初始化的最安全方法是使用它们的类的静态初始化器中(因为可以保证在其他初始化器之前运行该类),但是当多个类需要增强方法时,这会增加复杂性。 通常,使用大量元编程的应用程序以引导类结束,以确保初始化在适当的时间进行。

    在清单2中 ,我将三个Classifier方法添加到Integer 。 现在,Groovy中的所有整数都具有这些方法。 (Groovy没有原始数据类型的概念;即使Groovy中的常量也使用Integer作为基础数据类型。)在定义每种方法的代码块中,我可以访问预定义的delegate参数,该参数代表正在调用的对象的值类上的方法。

    初始化元编程方法后(请参见初始化元编程方法侧栏),可以“询问”有关类别的数字,如清单3所示:

    清单3.使用元编程对数字进行分类
    @Test void metaclass_classifiers() { def num = 28 assertTrue num.isPerfect() assertTrue 7.isDeficient() assertTrue 6.isPerfect() assertTrue 12.isAbundant() }

    清单3展示了新添加的同时作用于变量和常量的方法。 现在,将方法添加到Integer可能很简单,该方法返回特定数字的分类(可能作为枚举)。

    向现有类添加新方法本身并不是特别的“功能性”,即使它们调用的代码具有较强的功能性也是如此。 但是,无缝添加方法的功能使合并包含重要功能功能的第三方库(例如Functional Java库)变得容易。 在第二部分中 ,我使用Functional Java库实现了数字分类器,在这里我将使用它来创建无限的完美数字流。

    使用元编程映射数据类型

    Groovy本质上是Java的方言,因此引入第三方库(例如Functional Java)是微不足道的。 但是,我可以通过在数据类型之间执行一些元编程映射来使接缝不太明显,从而将这些库进一步编织到Groovy中。 Groovy具有本机关闭类型(使用Closure类)。 功能性Java还没有闭包的奢侈性(它依赖Java 5语法),从而迫使作者使用泛型和包含f()方法的常规F类。 使用Groovy ExpandoMetaClass ,我可以通过在两者之间创建映射方法来解决方法/关闭类型的差异。

    我要扩充的类是Functional Java的Stream类,它为无限列表提供了抽象。 我希望能够传递Groovy闭包代替Functional Java F实例,因此我向Stream类添加了重载方法,以将闭包映射到F的f()方法,如清单4所示:

    清单4.使用ExpandoMetaClass映射数据类型
    Stream.metaClass.filter = { c -> delegate.filter(c as fj.F) } // Stream.metaClass.filter = { Closure c -> delegate.filter(c as fj.F) } Stream.metaClass.getAt = { n -> delegate.index(n) } Stream.metaClass.getAt = { Range r -> r.collect { delegate.index(it) } }

    第一行在Stream上创建一个filter()方法,该方法接受闭包(代码块的c参数)。 第二行(带注释)与第一行相同,但为Closure添加了类型声明; 它不会影响Groovy执行代码的方式,但可能更适合作为文档。 代码块的主体调用Stream的预先存在的filter()方法,将Groovy闭包映射到Functional Java fj.F类。 我使用Groovy的半魔术符as运算符来执行映射。

    Groovy的as运算符将闭包强制为接口定义,从而允许闭包方法映射到接口所需的方法。 考虑清单5中的代码:

    清单5.使用as创建轻量级迭代器
    def h = [hasNext : { println "hasNext called"; return true}, next : {println "next called"}] as Iterator h.hasNext() h.next() println "h instanceof Iterator? " + (h instanceof Iterator)

    在清单5的示例中,我创建了具有两个名称-值对的哈希。 每个名称都是一个字符串(Groovy不需要使用双引号来分隔哈希键,因为默认情况下它们是字符串),并且值是代码块。 as运算符将此哈希映射到Iterator接口,该接口同时需要hasNext()和next()方法。 一旦执行了映射,就可以将哈希视为迭代器。 清单的最后一行显示true 。 如果我有一个单一方法的接口,或者当我希望接口中的所有方法都映射到单个闭包时,我可以省去哈希值,并直接将as将闭包映射到函数上。 回到清单4的第一行,我将传递的闭包映射到单方法F类。 在清单4中 ,我必须映射两个getAt方法(一个接受一个数字,另一个接受一个Range ),因为filter需要这些方法进行操作。

    使用这个新增强的Stream ,我可以处理一个无限序列,如清单6所示:

    清单6.在Groovy中使用无限功能Java流
    @Test void adding_methods_to_fj_classes() { def evens = Stream.range(0).filter { it % 2 == 0 } assertTrue(evens.take(5).asList() == [0, 2, 4, 6, 8]) assertTrue(evens[3..6] == [6, 8, 10, 12]) }

    在清单6中 ,我创建了一个从0开始的偶数整数的无限列表,方法是使用闭合块对其进行过滤。 您不能一次获得所有无限序列,因此必须take()获取任意数量的元素。 清单6的其余部分显示了测试断言,这些断言演示了流的工作方式。

    Groovy中的无限流

    在上一期中 ,我展示了如何在Groovy中实现一个惰性无限列表。 而不是手工创建它,为什么不依赖于Functional Java中的无限序列呢?

    要创建一个无限的完美数Stream ,我需要两个附加的Stream方法映射来理解Groovy闭包,如清单7所示:

    清单7.完美数流的两个附加方法映射
    Stream.metaClass.asList = { delegate.toCollection().asList() } Stream.metaClass.static.cons = { head, closure -> delegate.cons(head, closure as fj.P1) } // Stream.metaClass.static.cons = // { head, Closure c -> delegate.cons(head, ['_1':c] as fj.P1)}

    在清单7中 ,我创建了一个asList()转换方法,可以轻松地将Functional Java流转换为列表。 我实现的另一个方法是重载cons() ,它是Stream上构造新列表的方法。 创建无限列表时,数据结构通常包含第一个元素和一个闭包块,作为列表的尾部,在调用时会生成下一个元素。 对于我的Groovy完美数字流,我需要Functional Java来理解cons()可以接受Groovy闭包。

    如果使用as将单个闭包映射到具有多个方法的接口,则对我在该接口上调用的任何方法都执行该闭包。 在大多数情况下,这种简单映射样式适用于Functional Java类。 但是,有些方法需要fj.P1方法而不是fj.F方法。 在某些情况下,我仍然可以避免使用简单的映射,因为下游方法不依赖于P1任何其他方法。 如果需要更高的精度,我可能不得不使用清单7中注释行中所示的更复杂的映射,该映射必须使用映射到闭包的_1()方法创建一个哈希。 尽管该方法看起来很奇怪,但这是fj.P1类的标准方法,该方法返回第一个元素。

    一旦将我的元编程映射的方法放在Stream ,就可以使用清单1中的Classifier创建无限个完美数字流,如清单8所示:

    清单8.使用Functional Java和Groovy的无限个完美数字流
    import static fj.data.Stream.cons import static com.nealford.ft.metafunctionaljava.Classifier.nextPerfectNumberFrom def perfectNumbers(num) { cons(nextPerfectNumberFrom(num), { perfectNumbers(nextPerfectNumberFrom(num))}) } @Test void infinite_stream_of_perfect_nums_using_functional_java() { assertEquals([6, 28, 496], perfectNumbers(1).take(3).asList()) }

    我使用静态导入既为cons()从Java功能,并为自己的nextPerfectNumberFrom()从方法Classifier ,以使代码更简洁。 perfectNumbers()方法通过将种子号之后的第一个完美数作为第一个元素(第一个元素)并添加一个闭合块作为第二个元素,来返回无穷个完美数字序列(是, cons是一个动词)。 闭包块返回无限序列,其中下一个数字作为头,闭包计算另一个序列作为尾巴。 在测试中,我生成了一个从1开始的完美数字流,并获取接下来的三个完美数字并断言它们与列表匹配。

    结论

    当开发人员想到元编程时,他们通常只考虑自己的代码,而不考虑扩充别人的代码。 Groovy允许我不仅向诸如Integer类的内置类中而且向诸如Functional Java之类的第三方库中添加新方法。 将元编程和函数式编程相结合,可以用很少的代码获得强大的功能,从而创建无缝链接。

    尽管我可以直接从Groovy调用Functional Java类,但是与真正的闭包相比,该库的许多构建块都比较笨拙。 通过使用元编程,我可以映射Functional Java方法,以使它们了解便捷的Groovy数据结构,从而实现两全​​其美。 在Java定义本机闭包类型之前,开发人员经常需要在语言类型之间执行这些多语言映射:Groovy闭包和Scala闭包在字节码级别上不是一回事。 拥有Java标准将把这些对话推到运行时,并消除对映射的需求,就像我在这里展示的那样。 但是,在此之前,此功能可生成干净但功能强大的代码。

    在下一部分中,我将讨论一些优化,函数式编程使您的运行时可以使用备忘录的Groovy制作并显示示例。


    翻译自: https://www.ibm.com/developerworks/java/library/j-ft8/index.html

    相关资源:SPRING攻略 第2版 (带书签)(一)
    Processed: 0.011, SQL: 9