Java是否需要闭包?

Java是否需要闭包?

Does Java need closures?

最近,我一直在阅读有关下一版Java可能支持闭包的内容。 我感觉我对什么是闭包有一个非常牢固的掌握,但是我想不出一个可靠的例子说明它们如何使"面向对象"语言"更好"。 谁能给我一个特定的用例,在该用例中需要关闭(或首选关闭)?


作为一名Lisp程序员,我希望Java社区理解以下差异:作为对象与闭包的功能。

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

匿名函数并没有为语言增加太多,它们只是允许您以较短的方式编写函数。

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

如果您有使用某种语言的a),那么接下来的问题就是如何处理b)?有些语言有a),但没有b)。在函数式编程世界中,a)(函数)和b(函数作为闭包)如今已成为规范。 Smalltalk拥有a)(块是匿名函数)的时间很长,但是随后Smalltalk的一些方言增加了对b)(块作为闭包)的支持。

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

从实用的角度来看,匿名函数会添加一些简短的符号,您可以在其中传递或调用函数。那可能是一件好事。

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

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


他们没有使面向对象的语言更好。它们使实用语言更加实用。

如果您正在使用OO锤攻击问题-将所有内容都表示为对象之间的相互作用-那么闭包是没有意义的。在基于类的OO语言中,封闭是烟雾弥漫的后室,那里的工作完成了,但此后没有人谈论。从概念上讲,这是可恶的。

实际上,这非常方便。我真的不想定义一种新类型的对象来保存上下文,为它建立"填充"方法,实例化它,然后填充上下文...我只想告诉编译器,"看看,看看有什么我现在可以访问吗?这是我想要的上下文,这是我要用于它的代码-直到我需要它为止,一直坚持下去。

很棒的东西。


最明显的是,对于所有这些类,只有一个名为run()或actionPerformed()或类似方法的类,将进行伪替换。因此,您可以使用闭包代替创建带有嵌入式Runnable的线程。没有我们现在拥有的强大,但是更加方便和简洁。

那么我们需要关闭吗?不,他们会很高兴吗?当然,只要它们不会像我担心的那样发狂。


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


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

例如。对列表中的每个元素进行某种转换是非常常见的操作。这种较高阶的函数通常称为"地图"或"收集"。 (请参阅*。Groovy的传播运算符)。

例如,要使列表中的每个元素不加闭包,您可能会这样写:

1
2
3
4
5
6
List<Integer> squareInts(List<Integer> is){
   List<Integer> result = new ArrayList<Integer>(is.size());
   for (Integer i:is)
      result.add(i*i);
   return result;
}

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

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

(此处有关基本类型的装箱可能存在性能问题。)

正如Pop Catalin所解释的那样,还有另一个称为"选择"或"过滤器"的高阶函数:可用于获取列表中符合某些条件的所有元素。例如:

代替:

1
2
3
4
5
6
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;
}

相反,您可以编写类似

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

使用提案。

您可能会看到Groovy语法,它是Java语言的扩展,目前支持闭包。有关如何使用闭包的更多示例,请参见《 Groovy用户指南》中有关收集的章节。

备注:

关于"关闭"一词,可能需要一些澄清。我上面显示的内容严格来说是没有闭包的。它们只是"功能对象"。
闭包是可以捕获(或"封闭")围绕它的代码的(词法)上下文的所有内容。从这种意义上讲,Java现在有闭包,即匿名类:

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

I've been reading a lot lately about the next release of Java possibly supporting closures. I feel like I have a pretty firm grasp on what closures are, but I can't think of a solid example of how they would make an Object-Oriented language"better."

好吧,大多数使用"闭包"一词的人实际上是"功能对象",从这个意义上说,在某些情况下,例如当您需要在排序功能中使用自定义比较器时,功能对象使编写更简单的代码成为可能。

例如,在Python中:

1
2
3
4
5
def reversecmp(x, y):
   return y - x

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

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

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

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

1
2
3
4
5
6
7
8
9
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());

如您所见,它具有与闭包相同的效果,但是笨拙且冗长。但是,匿名内部类的添加消除了大多数痛苦:

1
2
3
4
5
6
7
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中函子类+匿名内部类的组合足以弥补真正函数对象的不足,从而不必添加它们。


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

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

1
2
3
4
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();
}

现在以不使用闭包的等效示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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不需要闭包即可工作,作为开发人员,您也不需要闭包,但是,它们很有用并提供好处,这意味着它们在生产语言中是理想的,并且其目标之一就是提高生产力。


Java从1.1开始就已经关闭了,只是一种非常繁琐且有限的方式。

每当您有一些描述的回调时,它们通常很有用。一个常见的情况是抽象出控制流,留下有趣的代码来调用带有外部控制流的闭包的算法。

每个示例都是一个简单的示例(尽管Java 1.5已经拥有了)。尽管您可以使用Java实现forEach方法,但它太冗长而无用。

现有Java已经有意义的一个示例是实现"环顾四周"惯用语,从而抽象化资源获取和释放。例如,可以在try / final内完成文件打开和关闭,而客户端代码不必正确获取细节。


当闭包最终到达Java中时,我会很高兴地摆脱所有自定义比较器类。

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

...看起来比...好得多

1
2
3
4
5
myArray.sort(new Comparator<MyClass>() {
   public int compare(MyClass a, MyClass b) {
      return a.myProperty().compareTo(b.myProperty();
   }
});

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

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

闭包将功能对象的概念提升为一流的实体-一种实体,它提高了代码的表达能力,而不是使代码变得更加复杂。

Tom Hawtin已经提到了一个最贴切的例子-实现Execute Around习惯用法,这是将RAII引入Java的唯一方法。几年前,当我第一次听说关闭即将到来时,我写了一篇有关该主题的博客文章。

具有讽刺意味的是,我认为闭包对Java有利的原因(更少的代码可以提高表达能力)可能是引起许多Java拥护者的不安。 Java的思维定式是"将所有内容都尽力拼写"。而且,闭包是向更实用的工作方式致敬的事实-我也认为这是一件好事,但可能会削弱Java社区中许多人珍视的纯OO信息。


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

这种扩展肯定会使该语言更具表现力,但我不确定
如果它真的适合其余语言。
Java被设计为一种面向对象的语言,不支持功能编程:新的语义是否易于理解? Java 6甚至没有功能,Java 7将具有匿名功能但没有"正常"功能吗?

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

因此,我目前的态度是坚持使用Java 6进行OOP(我希望Java 6仍会
会得到一段时间的支持),以防万一我真的对OOP + FP感兴趣,
看一下其他语言,例如Scala(Scala被定义为多语言,
从一开始的范例,并且可以与Java很好地集成),而不是切换
Java 7。

我认为Java之所以成功,是因为它结合了简单的语言和
强大的库和工具,而且我认为闭包之类的新功能不会
使它成为更好的编程语言。


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

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

  • 拉姆达州:本文在第一页或第二页中试图回答此处提出的问题。尽管我仍然发现它在参数方面很短,但是到处都是示例。
  • Lambda状态-库版本:这也非常有趣,因为它涵盖了惰性评估和并行性等优点。
  • Lambda表达式的翻译:基本上解释了Java编译器完成的减糖过程。

强制性语言的闭包(例如:JavaScript,C#,即将推出的C ++刷新)与匿名内部类不同。他们需要能够捕获对局部变量的可修改引用。 Java的内部类只能捕获本地final变量。

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

  • forwhiledo只是goto / if上的语法糖。
  • 内部类是指向类的语法糖,其字段指向外部类。
  • 泛型是语法转换糖。

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


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


Java闭包示例


匿名功能缺乏绑定[即如果外部上下文的变量(和方法参数,如果有一个封闭的方法)被声明为final,那么它们是可用的,但不能使用其他方法],我不太了解该限制的实际含义。

无论如何,我都会大量使用" final"。因此,如果我的意图是在闭包内部使用相同的对象,那么我的确会在封装范围内将这些对象声明为final。但是,让" closure "仅获得引用的副本,就像通过构造函数传递一样,那是错误的(事实上,这是如何完成的)。

如果闭包想要覆盖引用,那就去吧;这样就不会更改封闭范围看到的副本。

如果我们认为这会导致代码无法读取(例如,也许在构造函数调用a.i.c.时无法直接看到对象引用是什么),那么至少如何使语法不太冗长呢?斯卡拉?时髦?


可读性和可维护性呢...单行闭包更难理解和调试,imo
软件生命长久,您可以让具备基本语言知识的人员对其进行维护...因此,比单行代码更好地传播逻辑以便于维护...通常没有软件明星会在软件之后照看软件释放...


您可能想看一下Groovy,它是一种与Java最兼容的语言,可以在JRE上运行,但支持Closures。


不仅是本杰史密斯,而且我爱你可以做到的...

myArray.sort {it.myProperty}

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

我绝对喜欢这个功能。


推荐阅读