我最近一直在阅读关于下一版Java可能支持闭包的内容.我觉得我对封闭是什么有一个非常牢固的把握,但我想不出一个如何使面向对象语言"更好"的一个可靠的例子.任何人都可以给我一个特定的用例,需要关闭(甚至首选)?
作为一名Lisp程序员,我希望Java社区能够理解以下不同之处:作为对象和闭包的函数.
a)功能可以命名或匿名.但它们也可以成为自己的对象.这允许函数作为参数传递,从函数返回或存储在数据结构中.这意味着函数是编程语言中的第一类对象.
匿名函数不会对语言增加太多,它们只允许您以较短的方式编写函数.
b)闭包是一个功能加上一个绑定环境.闭包可以向下传递(作为参数)或向上传递(作为返回值).这允许函数引用其环境的变量,即使周围的代码不再有效.
如果你有a)用某种语言,那么问题出现了怎么办b)?有些语言有a),但有b).在函数式编程世界中,a)(函数)和b(函数作为闭包)现在是常态.Smalltalk 很长一段时间都有a)(块是匿名函数),但是Smalltalk的一些方言增加了对b)的支持(块作为闭包).
如果在语言中添加函数和闭包,可以想象你会得到一个稍微不同的编程模型.
从实用的角度来看,匿名函数添加了一些简短的表示法,您可以在其中传递或调用函数.这可能是一件好事.
闭包(函数加绑定)允许您创建一个可以访问某些变量的函数(例如,计数器值).现在,您可以将该函数存储在对象中,访问它并调用它.函数对象的上下文现在不仅是它有权访问的对象,而且还有它可以通过绑定访问的变量.这也很有用,但你可以看到变量绑定与对象变量的访问现在是一个问题:什么时候应该是一个词法变量(可以在一个闭包中访问),什么时候它应该是一个对象的变量(一个插槽).什么时候应该是一个闭包或一个对象?您可以以类似的方式使用它们.学生学习Scheme(一种Lisp方言)的常用编程练习是使用闭包编写一个简单的对象系统.
结果是更复杂的编程语言和更复杂的运行时模型.太复杂?
它们不会使面向对象的语言更好.它们使实用语言更加实用.
如果你用OO锤子攻击一个问题 - 把所有东西都表示为对象之间的相互作用 - 那么一个闭包没有任何意义.在基于类的OO语言中,闭包是充满烟雾的后室,其中的东西已经完成,但之后没有人谈论它.从概念上讲,这是令人憎恶的.
在实践中,它非常方便.我真的不想定义一个新类型的对象来保存上下文,为它建立"do stuff"方法,实例化它,并填充上下文...我只是想告诉编译器,"看,看看是什么我现在可以访问?这是我想要的上下文,这里是我想用它的代码 - 为我保留这个'直到我需要它".
很棒的东西.
最明显的事情是对所有那些只有一个名为run()或actionPerformed()或类似方法的类进行伪替换.因此,不是创建一个嵌入了Runnable的Thread,而是使用闭包.不比我们现在拥有的更强大,但更方便和简洁.
那么我们需要关闭吗?不,他们会很高兴吗?当然,只要他们不觉得狂奔,我担心他们会这样.
我想,为了支持核心函数式编程概念,你需要闭包.通过对闭包的支持,使代码更加优雅和可组合.此外,我喜欢将代码行作为参数传递给函数.
有一些非常有用的"高阶函数"可以使用闭包对列表进行操作.高阶函数是具有"函数对象"作为参数的函数.
例如,将一些转换应用于列表中的每个元素是一种非常常见的操作.这种高阶函数通常称为"map"或"collect".(参见Groovy的*.spread运算符).
例如,为了在没有闭包的情况下对列表中的每个元素进行平方,您可能会写:
ListsquareInts(List is){ List result = new ArrayList (is.size()); for (Integer i:is) result.add(i*i); return result; }
使用闭包和贴图以及建议的语法,您可以这样写:
is.map({Integer i => i*i})
(关于原始类型的装箱,这里可能存在性能问题.)
正如Pop Catalin所解释的那样,还有另一个更高阶的函数叫做'select'或'filter':它可以用来获取符合某些条件的列表中的所有元素.例如:
代替:
void onlyStringsWithMoreThan4Chars(Liststrings){ List result = new ArrayList (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不需要闭包,面向对象的语言可以使用中间对象来存储状态或执行操作(在Java的情况下是内部类)中执行闭包所做的一切.但是闭包是一个特征,因为它们极大地简化了代码并提高了可读性,从而提高了代码的可维护性.
我不是Java专家,但我使用的是C#3.5,闭包是我最喜欢的语言功能之一,例如以下面的语句为例:
// Example #1 with closures public IListGetFilteredCustomerList(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 IListGetFilteredCustomerList(string filter) { return new Customers.Where( new CustomerNameFiltrator(filter)); } ... public class CustomerNameFiltrator : IFilter { 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不需要闭包工作,作为一个你不需要它的开发人员,但是,它们是有用的并且提供好处,这意味着它们是一种生产语言所需的语言,其中一个目标就是生产力.
我最近一直在阅读关于下一版Java可能支持闭包的内容.我觉得我对封闭是什么有一个非常牢固的把握,但我想不出一个如何使面向对象语言"更好"的可靠例子.
好吧,大多数使用术语"闭包"的人实际上是指"函数对象",从这个意义上说,函数对象可以在某些情况下编写更简单的代码,例如在sort函数中需要自定义比较器时.
例如,在Python中:
def reversecmp(x, y): return y - x a = [4, 2, 5, 9, 11] a.sort(cmp=reversecmp)
这通过传递自定义比较functoin 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; } ... Lista = Arrays.asList(4, 2, 5, 9, 11); Collections.sort(a, new ReverseComparator());
正如您所看到的,它提供与闭包相同的效果,但更笨拙,更冗长.然而,添加匿名内部类可以消除大部分痛苦:
Lista = 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中的仿函数类+匿名内部类的组合足以弥补真正的函数对象的缺乏,使得它们的添加变得不必要.
自1.1以来,Java一直处于封闭状态,只是一种非常繁琐且有限的方式.
只要你有一些描述的回调,它们通常很有用.一个常见的情况是抽象出控制流,让有趣的代码调用一个没有外部控制流的闭包的算法.
一个简单的例子是for-each(虽然Java 1.5已经有了).虽然你可以在Java中实现一个forEach方法,但是它太冗长而无用.
现有Java已经有意义的一个例子是实现"执行"惯用法,从而抽象出资源获取和释放.例如,文件打开和关闭可以在try/finally中完成,而客户端代码不必正确地获取详细信息.