Java中的闭包模拟是否有意义?

时间:2009-11-27 12:06:58

标签: java

具有闭包的语言(例如Ruby)使得优雅的构造能够转换列表。假设我们有一个班级

class QueryTerm {
  String value;
  public String getValue() {...}
}

以及我们希望转换为其值列表List<QueryTerm> terms的术语List<String> values列表。

在Ruby中,我们可以编写如下内容:

values1 = terms.collect do |term|
    term.getValue()
end

Java迫使我们构造结果列表自己遍历术语集合(至少自引入foreach以来没有涉及迭代器或索引位置):< / p>

Collection<String> values2 = new HashSet<String>();
for (QueryTerm term : terms) {
    values2.add(term.getValue());
}

Apache CommonsGoogle Collections2尝试使用匿名类来模拟Java中的闭包:

Collection<String> values3 = Collections2.transform(terms, new Function<QueryTerm, String>() {
    @Override
    public String apply(QueryTerm term) {
        return term.getValue();
    }
});

奇怪的是,这个版本有更多的代码行和比原始版本更多的字符!我认为由于这个原因很难理解。因此,当我在Apache Commons中看到它时,我就驳回了这个想法。然而,最近我在谷歌收藏中看到它,我开始怀疑我是否遗漏了一些东西。

因此我的问题:您对上述结构有何看法?您认为Collections2.transform()版本比默认版本更易读/可理解吗?我完全错过了什么吗?

祝你好运, 约翰内斯

7 个答案:

答案 0 :(得分:5)

此处使用的闭包与Command pattern几乎完全相同。我猜这里命令有用的任何原因都适用于此。当然,Command也可能比关闭稍微更完整,因为后者仅在功能级别上。但一个关键的观察是,这通常都是你需要的。

有一些优势对我来说很明显:

  • 宣布您的操作的功能风格更容易映射到我们对它们的思考方式,即这是我的功能,请将其应用于此数据。
  • 通过透明使用不同的应用/过滤/转换调用来实现转换的链接。您可以继续将它们链接在一起,并且您可以看到所有操作在一行中相互收集。当然,它周围还有很多样板文件,但是如果做得对,那么任何java程序员都可以读取它。
  • 控制反转:假设您想要在几个不同的线程上分解您的操作。如果您编写了自己的循环,那么您现在已经陷入困境:您必须自己将其分解为几个不同的循环。编写代码通常很难看,而且需要你创建几个可以传递给其他线程的Runnables。所以你真的只是再次使用闭包。

    其中一个JDK7提案显然无法完成这一切,只需提交您的数据并定义要在其上执行的过滤器,您就可以采用它的快乐方式。实际上你可以为此download the code(包额外166)。您可以在javadoc中查看此包的所有操作都可以删除并替换为闭包。所以这是一个很好的例子,为什么某种方法由于缺乏真正的闭包而失败,并且“模拟”闭包不再完全削减它。

实际上,通过传递一个简单的函数可以轻松解决大量问题,这个函数可以完成您希望它对数据执行的操作,并让框架填写所有其他内容。你现在可以使用匿名类来做到这一点,但适当的关闭至少会削减很多锅炉板。

我使用的一个框架使用了类似这种方法来删除大量样板文件Spring-Jdbc。在这里,您将JdbcTemplate传递给一个简单的函数,该函数从ResultSet中抓取您的对象,并将其与查询一起传递给template.query()。这减少了许多恶化和通常的JDBC代码的不可读性。如果你能保持这样的话,肯定会获胜。

答案 1 :(得分:4)

IMO,即使有更多行代码,在您的特定示例中,transform版本看起来更具可读性。为什么?由于这个词,转换。您立即知道正在集合中执行转换,而使用普通版本,您必须遵循代码流程以了解它的意图。一旦你掌握了这些新结构,你就会发现它们更具可读性。

答案 2 :(得分:2)

我喜欢他们并且很多地使用它们。我相信主要的好处不是语法,而是您可以预定义并重新使用上面声明为匿名类的代码。例如,您可以预定义可以传递给变换函数的标准变换,甚至是NullTransform,它不会为获取Null对象模式效果做任何事情。这允许您更具说明性的编码风格。

答案 3 :(得分:1)

bruno conde说的是什么。此外,某些IDE(例如Eclipse)可以隐藏匿名类,将Google Collection代码缩小为单行。现在 是可读的! :)

答案 4 :(得分:1)

在软件开发中,明确且易懂(阅读:可维护)比简洁更重要,除非你玩一次性代码或perl脚本: - )

如果你想要实现的是将对象列表转换为另一种类型的对象列表,那么第一个Java示例就代码意图而言是最清晰的,imho。

Google集合示例也明确说明了代码的用意是什么,尽管像Hannes所说的那样,我不会使用匿名类,而是使用具有有用名称的显式定义的类。

你在Ruby中使用的语法糖可以很容易地在你的代码中引入转换,当它们需要改变时,可能很容易错过一个。转换列表也不是一个非常便宜的功能,并且能够轻松使用它们带来过度使用的风险。

答案 5 :(得分:0)

这非常难看,人们渴望更好的语法。人们可以在一段时间后习惯它,但对于初学者来说这是令人生畏的。

忘记@Override - 代码已经混乱。 @Override解决了现实中不存在的问题,但人们现在正在使用它,好像它是一个关键的语义元素。

答案 6 :(得分:0)

函数式编程不是通过使用它可以节省的许多键击的问题,而是主要是它如何提高代码的可读性。例如,使用lambdaj,您可以转换代码段:

Collection<String> values2 = new HashSet<String>();
for (QueryTerm term : terms) {
    values2.add(term.getValue());
}

在这一个:

Collection<String> values2 = extract(terms, on(QueryTerm.class).getValue());

难道你不认为这种方式更具可读性吗?