提问人:Bill the Lizard 提问时间:9/9/2008 最后编辑:Bill the Lizard 更新时间:4/29/2015 访问量:7631
Java 需要闭包吗?
Does Java need closures?
问:
我最近读了很多关于Java的下一个版本可能支持闭包的文章。我觉得我对什么是闭包有相当深刻的理解,但我想不出一个可靠的例子来说明它们如何使面向对象语言“更好”。谁能给我一个需要(甚至首选)闭包的特定用例?
答:
最明显的事情是伪替换所有那些只有一个方法的类,称为 run() 或 actionPerformed() 或类似的东西。因此,与其创建嵌入了 Runnable 的 Thread,不如使用闭包。不比我们现在拥有的更强大,但更方便、更简洁。
那么我们需要关闭吗?不。他们会好吗?当然,只要他们不觉得自己被束缚住了,我担心他们会这样。
评论
我想为了支持核心函数式编程概念,你需要闭包。通过对闭包的支持,使代码更加优雅和可组合。另外,我喜欢将代码行作为参数传递给函数的想法。
评论
评论
它们不会使面向对象的语言变得更好。它们使实用语言更加实用。
如果你正在用 OO 锤子来解决问题——将所有内容表示为对象之间的交互——那么闭包就没有意义了。在基于类的 OO 语言中,关闭是烟雾弥漫的密室,在那里事情已经完成,但事后没有人谈论它。从概念上讲,这是令人憎恶的。
在实践中,它非常方便。我真的不想定义一种新型的对象来保存上下文,为它建立“做东西”方法,实例化它,并填充上下文......我只想告诉编译器,“看,看看我现在可以访问什么?这就是我想要的上下文,这是我想使用它的代码 - 为我保留它,直到我需要它”。
太棒了。
评论
Java 从 1.1 开始就有了闭包,只是以一种非常繁琐和有限的方式。
只要您有某种描述的回调,它们通常都很有用。一种常见的情况是抽象出控制流,让有趣的代码调用具有没有外部控制流的闭包的算法。
一个简单的例子是 for-each(尽管 Java 1.5 已经有了)。虽然你可以在 Java 中实现一个 forEach 方法,但它太冗长了,没有用。
对于现有的 Java 来说,一个已经很有意义的例子是实现“执行周围”的习惯,即资源获取和释放被抽象化。例如,文件打开和关闭可以在 try/finally 中完成,而无需客户端代码正确获取详细信息。
作为一个试图自学lisp以试图成为一个更好的程序员的java开发人员,我想说的是,我希望看到Josh Block关于闭包的提议得以实现。我发现自己使用匿名内部类来表达一些事情,比如在聚合一些数据时如何处理列表的每个元素。最好将其表示为闭包,而不必创建一个抽象类。
评论
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 不需要闭包来工作,作为开发人员,您也不需要它们,但是,它们很有用并提供好处,这意味着它们在生产语言中是可取的,它的目标之一是生产力。
评论
有一些非常有用的“高阶函数”可以使用闭包对列表进行操作。高阶函数是以“函数对象”为参数的函数。
例如,对列表中的每个元素应用一些转换是一个非常常见的操作。这种高阶函数通常称为“映射”或“收集”。(参见 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
}
};
}
评论
当闭包最终出现在 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();
}
});
不仅是那个 benjismith,而且我喜欢你如何做到......
myArray.sort{ it.myProperty }
只有当属性的自然语言比较不符合您的需求时,您才需要显示的更详细的比较器。
我非常喜欢这个功能。
我最近读了很多关于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 中函子类 + 匿名内部类的组合足以弥补真正函数对象的不足,使它们的添加变得没有必要。
作为一名 Lisp 程序员,我希望 Java 社区能够理解以下区别:作为对象的函数与闭包函数。
a) 函数可以是命名的,也可以是匿名的。但它们也可以是自己的对象。这允许函数作为参数传递、从函数返回或存储在数据结构中。这意味着函数是编程语言中的第一类对象。
匿名函数不会给语言增加太多内容,它们只是允许您以更短的方式编写函数。
b) 闭包是一个函数加上一个绑定环境。闭包可以向下传递(作为参数)或向上返回(作为返回值)。这允许函数引用其环境的变量,即使周围的代码不再处于活动状态。
如果你有某种语言的 a),那么问题就来了,如何处理 b)?有些语言有 a),但没有 b)。在函数式编程领域,a)(函数)和b(作为闭包的函数)是当今的常态。Smalltalk 有很长一段时间都有 a)(块是匿名函数),但后来 Smalltalk 的一些方言增加了对 b) 的支持(块作为闭包)。
您可以想象,如果向语言添加函数和闭包,则会得到一个略有不同的编程模型。
从实用的角度来看,匿名函数添加了一些简短的表示法,您可以在其中传递或调用函数。这可能是一件好事。
例如,闭包(函数加绑定)允许您创建一个可以访问某些变量(例如计数器值)的函数。现在,您可以将该函数存储在对象中,访问它并调用它。函数对象的上下文现在不仅是它有权访问的对象,还包括它可以通过绑定访问的变量。这也很有用,但你可以看到,变量绑定与对对象变量的访问现在是一个问题:什么时候应该是词法变量(可以在闭包中访问),什么时候应该是某个对象的变量(槽)。什么时候应该成为闭包或对象?您可以以类似的方式使用两者。对于学习 Scheme(一种 Lisp 方言)的学生来说,一个常见的编程练习是使用闭包编写一个简单的对象系统。
结果是更复杂的编程语言和更复杂的运行时模型。太复杂了?
评论
命令式语言中的闭包(例如:JavaScript、C#、即将推出的 C++ 刷新)与匿名内部类不同。它们需要能够捕获对局部变量的可修改引用。Java 的内部类只能捕获局部变量。final
几乎任何语言功能都可以被批评为非必要:
for
、 、 都只是 / 上的句法糖。while
do
goto
if
- 内部类是类的句法糖,其字段指向外部类。
- 泛型是语法糖对演员表。
完全相同的“非必要”参数应该阻止包含上述所有功能。
评论
一些人已经说过,或者暗示过,闭包只是句法糖 - 做你已经可以用匿名内部类做的事情,并使传递参数更方便。
它们是句法糖,就像 Java 是汇编程序的语法糖一样(为了论证,“汇编程序”可以是字节码)。换句话说,它们提高了抽象水平,这是一个重要的概念。
闭包将函数即对象的概念提升为第一类实体 - 一个增加代码表现力的实体,而不是用更多的样板来弄乱它。
Tom Hawtin 已经提到了一个贴近我内心的例子 - 实现 Execute Around 习惯法,这几乎是将 RAII 引入 Java 的唯一方法。几年前,当我第一次听说可能要关闭时,我写了一篇关于这个主题的博客文章。
具有讽刺意味的是,我认为闭包对 Java 有好处的原因(用更少的代码提高表现力)可能正是让许多 Java 拥护者感到不安的原因。Java 有一种“把一切都拼出来”的心态。这一点以及闭包是对一种更实用的做事方式的认可——我也认为这是一件好事,但可能会淡化 Java 社区中许多人所珍视的纯 OO 信息。
评论
可读性和可维护性如何?单行闭包更难理解和调试,IMO 软件有很长的生命力,你可以让对这门语言有基本了解的人来维护它......因此,比单行更好地展开逻辑,以便于维护......你通常没有一个软件明星在软件发布后照顾它......
评论
你可能想看看Groovy,它是一种与Java兼容的语言,运行在JRE上,但支持闭包。
我一直在思考这个非常有趣的问题的话题 最近几天。首先,如果我理解正确的话,Java 已经有了 闭包的一些基本概念(通过匿名类定义),但新功能 将要引入的是对基于匿名函数的闭包的支持。
这个扩展肯定会使语言更具表现力,但我不确定 如果它真的适合语言的其余部分。 Java 被设计为一种不支持函数式编程的面向对象语言:新的语义是否易于理解?Java 6 甚至没有函数,Java 7 会有匿名函数但没有“普通”函数吗?
我的印象是,作为新的编程风格或范式,如函数式 编程变得越来越流行,每个人都想在他们的 最喜欢的OOP语言。这是可以理解的:一个人想继续使用 他们在采用新功能时熟悉的语言。但以这种方式 一种语言可能会变得非常复杂并失去连贯性。
所以我目前的态度是坚持使用 Java 6 进行 OOP(我希望 Java 6 仍然会 支持一段时间)并且,如果我真的对做 OOP + FP 感兴趣, 看看其他一些语言,比如 Scala(Scala 被定义为多 范式,并且可以很好地与 Java 集成)而不是切换 到 Java 7。
我认为 Java 的成功归功于它结合了简单的语言和 强大的库和工具,我不认为像闭包这样的新功能会 使其成为更好的编程语言。
评论
现在 JDK8 即将发布,有更多的信息可以丰富这个问题的答案。
Oracle 的语言架构师 Bria Goetz 发表了一系列关于 Java 中 lambda 表达式当前状态的论文(尚未草稿)。它还涵盖了闭包,因为他们计划在即将到来的 JDK 中发布它们,它应该在 2013 年 1 月左右完成代码,应该在 2013 年年中左右发布。
- Lambda 的状态:本文在第一页或第二页试图回答这里提出的问题。虽然我仍然觉得它在论证上很短,但充满了例子。
- The State of Lambda - Libraries Edition:这也非常有趣,因为它涵盖了惰性计算和并行性等优点。
- Lambda 表达式的翻译:基本上解释了 Java 编译器完成的脱糖过程。
评论
匿名函数中缺乏绑定[即,如果外部上下文的变量(以及方法参数,如果有封闭方法)被声明为最终的,那么它们是可用的,但除此之外不是],我不太明白这种限制实际上买了什么。
无论如何,我都大量使用“最终”。因此,如果我的意图是在闭包中使用相同的对象,我确实会在封闭范围内声明这些对象是最终的。但是,让“闭包 [java a.i.c.]”只是获取引用的副本,就好像通过构造函数传递一样(实际上就是这样完成的),这有什么问题呢?
如果闭包想要覆盖引用,那就这样吧;它将在不更改封闭范围看到的副本的情况下执行此操作。
如果我们认为这会导致不可读的代码(例如,在构造函数调用 a.i.c. 时查看对象引用是什么可能并不简单),那么至少让语法不那么冗长怎么样?斯卡拉?槽的?
评论
上一个:Java 中闭包的现状如何?
下一个:奇怪的嘘声语言语法
评论