groovy邮件功能_Groovy中的功能部件,第2部分

本文是系列文章的一部分,探讨了如何利用Groovy的元编程和函数式编程特性来扩展和融合不同的编程风格。通过元编程,可以在Groovy中为类添加方法,例如将Functional Java库的功能融入Groovy。文章展示了如何通过元编程向Java的Integer类添加分类方法,并创建无限流,特别是完美数字流。此外,还讨论了元编程如何在不干扰函数式编程的情况下,使得代码更具功能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

通过ExpandoMetaClass打开类

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类添加了重载方法,以将闭包映射到Ff()方法,如清单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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值