Java 需要闭包吗?

Does Java need closures?

提问人:Bill the Lizard 提问时间:9/9/2008 最后编辑:Bill the Lizard 更新时间:4/29/2015 访问量:7631

问:

我最近读了很多关于Java的下一个版本可能支持闭包的文章。我觉得我对什么是闭包有相当深刻的理解,但我想不出一个可靠的例子来说明它们如何使面向对象语言“更好”。谁能给我一个需要(甚至首选)闭包的特定用例?

Java 闭包

评论


答:

37赞 jodonnell 9/9/2008 #1

最明显的事情是伪替换所有那些只有一个方法的类,称为 run() 或 actionPerformed() 或类似的东西。因此,与其创建嵌入了 Runnable 的 Thread,不如使用闭包。不比我们现在拥有的更强大,但更方便、更简洁。

那么我们需要关闭吗?不。他们会好吗?当然,只要他们不觉得自己被束缚住了,我担心他们会这样。

评论

3赞 Bill the Lizard 9/11/2008
感谢您加强了我的怀疑,即闭包只是匿名内部类的句法糖。
16赞 Daniel Earwicker 4/4/2009
如果它们像JavaScript / C# / C++0闭包一样完整,它们将比匿名内部类更强大,因为它们将能够捕获对局部变量的可修改引用,而不仅仅是最终变量的只读副本。
2赞 Bart van Heukelom 4/12/2010
@Daniel Earwicker:您可以通过将可修改变量包装在容器对象中(将其放在堆上)来模拟这一点,然后将其分配给本地最终变量。
3赞 Daniel Earwicker 4/12/2010
@Bart van Heukelom - 你确实可以,甚至更好的是,你可以使用一种为你做到这一点的语言!:)
1赞 1/12/2011
@Bill:闭包与匿名函数或内部类不同,尽管差异很微妙。查看 en.wikipedia.org/wiki/Closure_%28computer_science%29
18赞 Gulzar Nazim 9/9/2008 #2

我想为了支持核心函数式编程概念,你需要闭包。通过对闭包的支持,使代码更加优雅和可组合。另外,我喜欢将代码行作为参数传递给函数的想法。

评论

1赞 Boris Terzic 9/9/2008
同意,只是为了方便的地图/过滤器实现,这是值得的。更不用说匿名内部类的大多数用途了。可读性和代码大小很重要。
1赞 jfs 9/9/2008 #3

Java 闭包示例

评论

0赞 jfs 9/9/2008
这是谷歌搜索 java 闭包示例返回的第一个链接
42赞 Shog9 9/9/2008 #4

它们不会使面向对象的语言变得更好。它们使实用语言更加实用。

如果你正在用 OO 锤子来解决问题——将所有内容表示为对象之间的交互——那么闭包就没有意义了。在基于类的 OO 语言中,关闭是烟雾弥漫的密室,在那里事情已经完成,但事后没有人谈论它。从概念上讲,这是令人憎恶的。

在实践中,它非常方便。我真的不想定义一种新型的对象来保存上下文,为它建立“做东西”方法,实例化它,并填充上下文......我只想告诉编译器,“看,看看我现在可以访问什么?这就是我想要的上下文,这是我想使用它的代码 - 为我保留它,直到我需要它”。

太棒了。

评论

1赞 Giorgio 3/10/2012
-1:“它们不会使面向对象的语言变得更好。它们使实用语言更加实用“:我强烈不同意这个原则。也许闭包会让 Java 更实用,但原则上不应该为了让语言“更实用”而牺牲语言的连贯性。国际海事组织(IMO)必须有另一个更强烈的动机来引入关闭。一门语言,如果语义非常异质,而且有很多特殊情况,只是为了“更实用”,就更难学习了。编程语言应该基于概念而不是习语。
5赞 Tom Hawtin - tackline 9/9/2008 #5

Java 从 1.1 开始就有了闭包,只是以一种非常繁琐和有限的方式。

只要您有某种描述的回调,它们通常都很有用。一种常见的情况是抽象出控制流,让有趣的代码调用具有没有外部控制流的闭包的算法。

一个简单的例子是 for-each(尽管 Java 1.5 已经有了)。虽然你可以在 Java 中实现一个 forEach 方法,但它太冗长了,没有用。

对于现有的 Java 来说,一个已经很有意义的例子是实现“执行周围”的习惯,即资源获取和释放被抽象化。例如,文件打开和关闭可以在 try/finally 中完成,而无需客户端代码正确获取详细信息。

2赞 l0st3d 9/9/2008 #6

作为一个试图自学lisp以试图成为一个更好的程序员的java开发人员,我想说的是,我希望看到Josh Block关于闭包的提议得以实现。我发现自己使用匿名内部类来表达一些事情,比如在聚合一些数据时如何处理列表的每个元素。最好将其表示为闭包,而不必创建一个抽象类。

评论

0赞 Tom Hawtin - tackline 9/9/2008
你所说的“抽象类”是指匿名的内层?
0赞 Bill the Lizard 4/4/2009
我认为这就是你所指的提议。docs.google.com/Doc.aspx?id=k73_1ggr36h
0赞 l0st3d 4/9/2009
是的,汤姆 - 我的意思是 - 对不起,看起来是比尔 - 谢谢
8赞 Pop Catalin 9/10/2008 #7

Java 不需要闭包,面向对象的语言可以使用中间对象来存储状态或执行操作(在 Java 的情况下是内部类)来完成闭包所做的一切。 但是,闭包作为一项功能是可取的,因为它们大大简化了代码并提高了可读性,从而提高了代码的可维护性。

我不是 Java 专家,但我使用的是 C# 3.5,闭包是我最喜欢的语言特性之一,例如以下语句为例:

// Example #1 with closures
public IList<Customer> GetFilteredCustomerList(string filter) {
    //Here a closure is created around the filter parameter
    return Customers.Where( c => c.Name.Contains(filter)).ToList();
}

现在举一个不使用闭包的等效示例

//Example #2 without closures, using just basic OO techniques
public IList<Customer> GetFilteredCustomerList(string filter) {
    return new Customers.Where( new CustomerNameFiltrator(filter));
}
... 
public class CustomerNameFiltrator : IFilter<Customer> {
    private string _filter;
    public  CustomerNameFiltrator(string filter) {
         _filter = filter;
    }
    public bool Filter(Customer customer) {
        return customer.Name.Contains( _filter);
    }
}

我知道这是 C# 而不是 Java,但想法是一样的,闭包对于简洁性很有用,并使代码更短、更易读。在幕后,C# 3.5 的闭包做了一些看起来与示例 #2 非常相似的事情,这意味着编译器在幕后创建一个私有类并将“filter”参数传递给它。

Java 不需要闭包来工作,作为开发人员,您也不需要它们,但是,它们很有用并提供好处,这意味着它们在生产语言中是可取的,它的目标之一是生产力。

评论

3赞 Michael Borgwardt 1/24/2009
请注意,当您使用匿名类时,差异要小得多
1赞 Pop Catalin 11/15/2010
@Michael Borgwardt,请注意,当你分配了闭包和函数组合时,差异要大得多。
12赞 jrudolph 9/15/2008 #8

有一些非常有用的“高阶函数”可以使用闭包对列表进行操作。高阶函数是以“函数对象”为参数的函数。

例如,对列表中的每个元素应用一些转换是一个非常常见的操作。这种高阶函数通常称为“映射”或“收集”。(参见 Groovy 的 *. spread 运算符)。

例如,要对列表中的每个元素进行平方,而不带闭包,您可能会这样写:

List<Integer> squareInts(List<Integer> is){
   List<Integer> result = new ArrayList<Integer>(is.size());
   for (Integer i:is)
      result.add(i*i);
   return result;
}

使用闭包和映射以及建议的语法,您可以这样编写:

is.map({Integer i => i*i})

(这里可能存在关于基元类型装箱的性能问题。

正如Pop Catalin所解释的那样,还有另一个高阶函数,称为“select”或“filter”:它可以用来获取列表中符合某种条件的所有元素。例如:

而不是:

void onlyStringsWithMoreThan4Chars(List<String> strings){
   List<String> result = new ArrayList<String>(str.size()); // should be enough
   for (String str:strings)
      if (str.length() > 4) result.add(str);
   return result;
}

相反,你可以写这样的东西

strings.select({String str => str.length() > 4});

使用提案。

你可以看看 Groovy 语法,它是 Java 语言的扩展,现在支持闭包。请参阅《Groovy 用户指南》中关于集合的章节,了解更多示例,了解如何使用闭包。

备注:

关于“关闭”一词,也许需要一些澄清。我上面展示的内容严格来说是没有闭合的。它们只是“函数对象”。 闭包是可以捕获或“关闭”围绕它的代码的(词法)上下文的所有内容。从这个意义上说,现在 Java 中有闭包,即匿名类:

Runnable createStringPrintingRunnable(final String str){
    return new Runnable(){
       public void run(){
          System.out.println(str); // this accesses a variable from an outer scope
       }
    };
}

评论

0赞 Logan Capaldo 2/4/2009
因了解(并解释)闭包和作为值的函数之间的区别而点赞
3赞 benjismith 2/4/2009 #9

当闭包最终出现在 Java 中时,我将兴高采烈地摆脱我所有的自定义比较器类。

myArray.sort( (a, b) => a.myProperty().compareTo(b.myProperty() );

...看起来比...

myArray.sort(new Comparator<MyClass>() {
   public int compare(MyClass a, MyClass b) {
      return a.myProperty().compareTo(b.myProperty();
   }
});
0赞 Rick R 4/4/2009 #10

不仅是那个 benjismith,而且我喜欢你如何做到......

myArray.sort{ it.myProperty }

只有当属性的自然语言比较不符合您的需求时,您才需要显示的更详细的比较器。

我非常喜欢这个功能。

8赞 cygil 4/4/2009 #11

我最近读了很多关于Java的下一个版本可能支持闭包的文章。我觉得我对什么是闭包有相当深刻的理解,但我想不出一个可靠的例子来说明它们如何使面向对象的语言“更好”。

好吧,大多数使用术语“闭包”的人实际上是指“函数对象”,从这个意义上说,函数对象可以在某些情况下编写更简单的代码,例如当您需要排序函数中的自定义比较器时。

例如,在 Python 中:

def reversecmp(x, y):
   return y - x

a = [4, 2, 5, 9, 11]
a.sort(cmp=reversecmp)

这将通过传递自定义比较函数 reversecmp,以相反的顺序对列表 a 进行排序。lambda 运算符的添加使事情变得更加紧凑:

a = [4, 2, 5, 9, 11]
a.sort(cmp=lambda x, y : y - x)

Java 没有函数对象,因此它使用“函子类”来模拟它们。在 Java 中,通过实现 Comparator 类的自定义版本并将其传递给 sort 函数来执行等效操作:

class ReverseComparator implements Comparator {
   public compare(Object x, Object y) {
      return (Integer) y - (Integer) x;
   }

...

List<Integer> a = Arrays.asList(4, 2, 5, 9, 11);
Collections.sort(a, new ReverseComparator());

正如你所看到的,它提供了与闭包相同的效果,但更笨拙、更冗长。但是,匿名内部类的添加消除了大部分痛苦:

List<Integer> a = Arrays.asList(4, 2, 5, 9, 11);
Comparator reverse = new Comparator() {
   public Compare(Object x, Object y) {
       return (Integer) y - (Integer) x;
   }
}
Collections.sort(a, reverse);

因此,我想说的是,Java 中函子类 + 匿名内部类的组合足以弥补真正函数对象的不足,使它们的添加变得没有必要。

72赞 Rainer Joswig 4/4/2009 #12

作为一名 Lisp 程序员,我希望 Java 社区能够理解以下区别:作为对象的函数与闭包函数

a) 函数可以是命名的,也可以是匿名的。但它们也可以是自己的对象。这允许函数作为参数传递、从函数返回或存储在数据结构中。这意味着函数是编程语言中的第一类对象。

匿名函数不会给语言增加太多内容,它们只是允许您以更短的方式编写函数。

b) 闭包是一个函数加上一个绑定环境。闭包可以向下传递(作为参数)或向上返回(作为返回值)。这允许函数引用其环境的变量,即使周围的代码不再处于活动状态。

如果你有某种语言的 a),那么问题就来了,如何处理 b)?有些语言有 a),但没有 b)。在函数式编程领域,a)(函数)和b(作为闭包的函数)是当今的常态。Smalltalk 有很长一段时间都有 a)(块是匿名函数),但后来 Smalltalk 的一些方言增加了对 b) 的支持(作为闭包)。

您可以想象,如果向语言添加函数和闭包,则会得到一个略有不同的编程模型。

从实用的角度来看,匿名函数添加了一些简短的表示法,您可以在其中传递或调用函数。这可能是一件好事。

例如,闭包(函数加绑定)允许您创建一个可以访问某些变量(例如计数器值)的函数。现在,您可以将该函数存储在对象中,访问它并调用它。函数对象的上下文现在不仅是它有权访问的对象,还包括它可以通过绑定访问的变量。这也很有用,但你可以看到,变量绑定与对对象变量的访问现在是一个问题:什么时候应该是词法变量(可以在闭包中访问),什么时候应该是某个对象的变量(槽)。什么时候应该成为闭包或对象?您可以以类似的方式使用两者。对于学习 Scheme(一种 Lisp 方言)的学生来说,一个常见的编程练习是使用闭包编写一个简单的对象系统。

结果是更复杂的编程语言和更复杂的运行时模型。太复杂了?

评论

1赞 blank 4/4/2009
如果可以的话,+2。这是关于 java 闭包的讨论中“缺失的环节”。值得一个问题,Rainer。
1赞 WolfmanDragon 11/25/2009
这是我遇到的对闭合的最好描述。我现在明白为什么新的关闭实现仅限于本地。危险威尔·罗宾逊,危险!
3赞 OscarRyz 5/28/2010
+1 现在我明白为什么 Joshua Bloch 说 Java 已经有闭包了(以匿名内部类的形式)看完你说的话,我同意。a.i.c的问题在于它们太冗长了。
0赞 Oliver Dixon 2/19/2016
如何使用瓶盖进行型式安全?一个抽象的匿名类比一个闭包更清晰吗?
2赞 Daniel Earwicker 4/4/2009 #13

命令式语言中的闭包(例如:JavaScript、C#、即将推出的 C++ 刷新)与匿名内部类不同。它们需要能够捕获对局部变量的可修改引用。Java 的内部类只能捕获局部变量。final

几乎任何语言功能都可以被批评为非必要:

  • for、 、 都只是 / 上的句法糖。whiledogotoif
  • 内部类是类的句法糖,其字段指向外部类。
  • 泛型是语法糖对演员表。

完全相同的“非必要”参数应该阻止包含上述所有功能。

评论

0赞 Bill the Lizard 4/4/2009
需要明确的是,我的问题并不是为了支持阻止包含闭包。此外,我同意您上面提到的功能之一的“非必要”论点。:)
0赞 Giorgio 7/25/2011
许多功能不是必需的。然而,恕我直言,人们应该看看一个功能的使用频率与它给语言语义和工具增加的复杂性相比。
3赞 philsquared 4/4/2009 #14

一些人已经说过,或者暗示过,闭包只是句法糖 - 做你已经可以用匿名内部类做的事情,并使传递参数更方便。

它们是句法糖,就像 Java 是汇编程序的语法糖一样(为了论证,“汇编程序”可以是字节码)。换句话说,它们提高了抽象水平,这是一个重要的概念。

闭包将函数即对象的概念提升为第一类实体 - 一个增加代码表现力的实体,而不是用更多的样板来弄乱它。

Tom Hawtin 已经提到了一个贴近我内心的例子 - 实现 Execute Around 习惯法,这几乎是将 RAII 引入 Java 的唯一方法。几年前,当我第一次听说可能要关闭时,我写了一篇关于这个主题的博客文章

具有讽刺意味的是,我认为闭包对 Java 有好处的原因(用更少的代码提高表现力)可能正是让许多 Java 拥护者感到不安的原因。Java 有一种“把一切都拼出来”的心态。这一点以及闭包是对一种更实用的做事方式的认可——我也认为这是一件好事,但可能会淡化 Java 社区中许多人所珍视的纯 OO 信息。

评论

1赞 Bill the Lizard 4/4/2009
我认为“把所有东西都拼出来”比“明确地拼出所有东西”更准确(至少在我的情况下)。有一个微妙的区别。
0赞 philsquared 4/4/2009
好吧 - 我会说这两者是截然不同的情况,有很大的交叉点。因此,也许如果我们谈论这两者的结合,我们就会得到它的诀窍:-)
1赞 seh 2/6/2010
一类函数与闭包是一个截然不同的概念。前者是指将对函数(或函数本身)的引用视为一个值——一个可以随意传递和调用的值。后者涉及捕获对定义点处词法上可用的值的引用,并通过在那里创建的函数的生存期延长其生存期。一个人可以在没有闭包的情况下拥有一流的函数,尽管我认为反之亦然。
0赞 philsquared 2/7/2010
好吧,它们是截然不同的想法,但一个是基于另一个的(因此您的最终评论中缺乏传递性)。我说过,“闭包将函数即对象的概念提升为第一类实体”——并不是说它们是一回事。事实是,就目前而言,Java 没有第一类函数的正确概念,但如果它有 Closures,它也会将其作为先决条件。这就是我真正的意思。但是,如果这还不清楚,那么您已经通过您的评论澄清了这一点是件好事:-)
0赞 Giorgio 2/16/2012
当你提高抽象级别时,你引入了新的语义,而不仅仅是使用句法糖。Java 不是汇编上的语法糖。
0赞 Neal Ravindran 3/19/2010 #15

可读性和可维护性如何?单行闭包更难理解和调试,IMO 软件有很长的生命力,你可以让对这门语言有基本了解的人来维护它......因此,比单行更好地展开逻辑,以便于维护......你通常没有一个软件明星在软件发布后照顾它......

评论

3赞 Neal Gafter 4/26/2010
你是凭经验说话的吗?我的经历恰恰相反。
0赞 user122299 1/12/2011 #16

你可能想看看Groovy,它是一种与Java兼容的语言,运行在JRE上,但支持闭包。

3赞 Giorgio 6/23/2011 #17

我一直在思考这个非常有趣的问题的话题 最近几天。首先,如果我理解正确的话,Java 已经有了 闭包的一些基本概念(通过匿名类定义),但新功能 将要引入的是对基于匿名函数的闭包的支持。

这个扩展肯定会使语言更具表现力,但我不确定 如果它真的适合语言的其余部分。 Java 被设计为一种不支持函数式编程的面向对象语言:新的语义是否易于理解?Java 6 甚至没有函数,Java 7 会有匿名函数但没有“普通”函数吗?

我的印象是,作为新的编程风格或范式,如函数式 编程变得越来越流行,每个人都想在他们的 最喜欢的OOP语言。这是可以理解的:一个人想继续使用 他们在采用新功能时熟悉的语言。但以这种方式 一种语言可能会变得非常复杂并失去连贯性。

所以我目前的态度是坚持使用 Java 6 进行 OOP(我希望 Java 6 仍然会 支持一段时间)并且,如果我真的对做 OOP + FP 感兴趣, 看看其他一些语言,比如 Scala(Scala 被定义为多 范式,并且可以很好地与 Java 集成)而不是切换 到 Java 7。

我认为 Java 的成功归功于它结合了简单的语言和 强大的库和工具,我不认为像闭包这样的新功能会 使其成为更好的编程语言。

评论

0赞 Nathan Moos 8/15/2011
这并不能回答问题,但+1为您的建议!就我个人而言,我确实使用 Scala,它的函数式编程很棒。
0赞 Giorgio 8/16/2011
感谢您的建议和投票。也许我应该更明确地表达我的陈述:我认为 Java 不需要闭包,并且这种扩展将使语言变得不必要地复杂。
0赞 Nathan Moos 8/19/2011
我认为你是对的。我认为 Java 从来没有将函数作为对象,因为 Sun 不希望它像 C 的函数指针那样(或者我在 Core Java 2 中读到的)。但是,有一些方法可以将函数实现为不涉及函数指针的对象。同样,Scala 就是最好的例子。
0赞 Giorgio 11/12/2011
我刚买了我的 Scala 书,盯着看它,我非常喜欢它!当然,我认为人们不应该摸索像Java或C++这样的语言,它们不是为支持FP而设计的。 Java仍然是我最喜欢的语言之一,但是如果想要OOP + FP,应该看看为此而设计的语言。
3赞 Edwin Dalorzo 5/13/2012 #18

现在 JDK8 即将发布,有更多的信息可以丰富这个问题的答案。

Oracle 的语言架构师 Bria Goetz 发表了一系列关于 Java 中 lambda 表达式当前状态的论文(尚未草稿)。它还涵盖了闭包,因为他们计划在即将到来的 JDK 中发布它们,它应该在 2013 年 1 月左右完成代码,应该在 2013 年年中左右发布。

评论

0赞 Ustaman Sangat 3/8/2013
Lexical scopeping 和 The State of Lambda 中的变量捕获是很好的读物;但是“有效的最终”,恕我直言,并不是一个好主意,编译器可能需要一个额外的通道来确定“有效最终性”,而且今天编译的代码明天开始中断,因为距离闭包“英里”的新编码行将引用转换为可变。
0赞 Edwin Dalorzo 3/8/2013
@UstamanSangat 我的理解是,在 Java 中,闭包中的所有自由变量实际上都是最终的,无论您是否明确声明它们。我已经有一段时间没有阅读翻译策略了,所以我不知道这是否只是一个编译器的东西,或者它是否以某种方式在 JVM 中被检测出来。
0赞 Ustaman Sangat 3/8/2013
同意,我的观点主要是关于距离闭包 20 行的新代码行,将以前有效的最终变量更改为可变变量。当该代码无法编译时,不太成熟的开发人员可能会感到困惑。但我认为 IDE 总能提供帮助。
0赞 Ustaman Sangat 3/8/2013 #19

匿名函数中缺乏绑定[即,如果外部上下文的变量(以及方法参数,如果有封闭方法)被声明为最终的,那么它们是可用的,但除此之外不是],我不太明白这种限制实际上买了什么。

无论如何,我都大量使用“最终”。因此,如果我的意图是在闭包中使用相同的对象,我确实会在封闭范围内声明这些对象是最终的。但是,让“闭包 [java a.i.c.]”只是获取引用的副本,就好像通过构造函数传递一样(实际上就是这样完成的),这有什么问题呢?

如果闭包想要覆盖引用,那就这样吧;它将在不更改封闭范围看到的副本的情况下执行此操作。

如果我们认为这会导致不可读的代码(例如,在构造函数调用 a.i.c. 时查看对象引用是什么可能并不简单),那么至少让语法不那么冗长怎么样?斯卡拉?槽的?

评论

0赞 Ustaman Sangat 3/8/2013
@Edwin Dolarzo 的链接很好读。需要知道 Scala 和 Groovy 等已经采取了什么方法