Java中的匿名类和闭包有什么区别?

35

看起来匿名类提供了闭包的基本功能,这是真的吗?


21
为什么要点踩?他试图征求对闭包的观点的反馈,如果他的观点是错误的,请解释原因,而不是点踩。 - Nix
我建议阅读博客http://fishbowl.pastiche.org/2003/05/16/closures_and_java_a_tutorial/上的“Java中的块”,它会带你了解闭包与内部类的区别。 - Nix
其实这是个好问题。匿名类经常被与闭包进行比较(通常只有忽略差异的Java程序员这样做)。 - Stein G. Strindhaug
1
这是一个好问题。顺便说一下:维基百科的文章将Java的内部类列为(模拟的?)闭包:http://en.wikipedia.org/wiki/Closure_(computer_science)#Java。 - musiKk
3个回答

15
几乎没有区别。实际上,有一句关于闭包和对象的老话。闭包是穷人的对象,对象是穷人的闭包。两者在其可执行性方面同等强大。我们只是在讨论表现力问题。
在Java中,我们用匿名对象建模闭包。事实上,一个小小的历史是:最初Java具有在不使用final的情况下修改外部范围的能力。这对于在本地方法作用域中分配的对象来说可以运行正常,但是对于原始类型来说,这引起了很多争议。原始类型在堆栈上分配,因此为了使它们在外部方法执行后继续存在,Java必须在堆上分配内存并将这些成员移动到堆中。当时人们对垃圾收集非常陌生,并且他们不信任它,所以声明是Java不应该在没有程序员明确指令的情况下分配内存。为了达成妥协,Java决定使用final关键字。

http://madbean.com/2003/mb2003-49/

有趣的是,现在Java可以取消final关键字的限制,因为现在每个人都更加熟悉垃圾收集器,从语言角度来看,这是完全兼容的。虽然解决此问题的方法很简单,只需在匿名对象上定义实例变量,您可以随意修改这些变量。实际上,通过编译器向匿名类添加公共实例变量,并重写源代码以使用这些变量而不是堆栈变量,这可能是实现闭包样式引用本地作用域的简单方法。

public Object someFunction() {
   int someValue = 0;

   SomeAnonymousClass implementation = new SomeAnonymousClass() {
       public boolean callback() {
           someValue++;
       }
   }
   implementation.callback();
   return someValue;
}

将被重写为:

 public Object someFunction() {
   SomeAnonymousClass implementation = new SomeAnonymousClass() {
       public int someValue = 0;

       public boolean callback() {
           someValue++;
       }
   }
   implementation.callback();

   // all references to someValue could be rewritten to 
   // use this instance variable instead.
   return implementation.someValue;
}

我认为人们抱怨匿名内部类的原因更多是与静态类型和动态类型有关。在Java中,我们必须为匿名类的实现者和接受匿名类的代码定义一个约定的接口。我们必须这样做,以便我们可以在编译时检查所有类型。如果我们有一等函数,那么Java需要定义一种语法来声明方法的参数和返回类型作为数据类型,以保持类型安全性而成为一种静态类型语言。这几乎与定义接口一样复杂。(接口可以定义多个方法,用于声明一等方法的语法仅用于一个方法)。您可以将此视为简短的接口语法。在幕后,编译器可以在编译时将短形式符号转换为接口。
有很多事情可以做到Java中,以改善匿名类的体验,而不必放弃语言或进行重大手术。

9
就它们都影响“私有”作用域的情况来说,从非常有限的意义上讲,是的。然而,两者之间有太多的差异,以至于答案几乎可以是否定的。
由于Java缺乏处理代码块作为真正R值的能力,内部类无法像通常在continuations中执行的那样传递代码块。因此,闭包作为continuation技术是完全缺失的。
虽然持有内部类可以延长将要被垃圾回收的类的生命周期(类似于闭包在重新绑定到闭包时保持变量活动状态),但Java通过绑定进行重命名的能力受限于遵守现有的Java语法。
为了允许线程使用Java的线程争用模型正确地不覆盖彼此的数据,内部类进一步受到限制,只能访问被保证不会被破坏的数据,即final locals。
这完全忽略了其他内部类(也称为静态内部类),它们在感觉上略有不同。换句话说,它涉及到了一些闭包可以处理的项目,但未达到大多数人认为必要的最低要求,以便被称为闭包。

1
有点像说“C宏提供了Lisp宏的基本功能”;有点正确,但大部分是错误的。 ;) - Stein G. Strindhaug
这个答案应该被更新(即Java 8)。您应该更详细地解释一下闭包的定义。例如,根据某些人对闭包的定义,Haskell 没有有效的闭包(因为您无法修改绑定的变量... 就像 Java 的 final)。目前,变量绑定的 final 主要是为了向后兼容。 - Adam Gent
有趣的是,得票最高的答案——其实也没有得到很高的票数——持有不同的观点。当然,现在这个问题在SO上会立即被关闭,所以就这样吧。 - Dan Rosenstark
1
有趣的是,我没有看到其他答案有不同的观点,但它淡化了作用域的相关性,即使它的例子突出了作用域的差异(这很奇怪,但这是一个奇怪的世界)。从功能上讲,两者相似,只是我的答案试图比较两个不同的世界(纯函数与纯OO),而另一个则大多展示了如何使用对象模拟函数构造(通过使用对象对其进行建模)。声明“对象是闭包的穷人版”是不正确的(但很流行),因为对象是闭包可用的数学可能性的子集。 - Edwin Buck
1
@DanRosenstark 有一些有趣的阅读材料 http://c2.com/cgi/wiki?ClosuresAndObjectsAreEquivalent,详细解释了为什么对象和闭包经常被视为等效,以及它们为什么不是等效的,即使你仍然试图将它们视为等效,它们的用法也是如此不同,以至于将它们视为等效可能会带来更多的伤害而不是好处。 "要点"是闭包可以完全覆盖对象的实用性,但对象只能部分覆盖闭包的实用性。 - Edwin Buck
显示剩余4条评论

4

在我看来,它们都有类似的作用,但闭包旨在更加简洁并可能提供更多功能。

假设您想使用匿名类来使用本地变量。

final int[] i = { 0 };
final double[] d = { 0.0 };

Runnable run = new Runnable() { 
    public void run() {
        d[0] = i[0] * 1.5;
    }
};
executor.submit(run);

闭包可以避免大部分模板式编码,让你只需书写所需内容。
int i = 0;
double d = 0.0;

Runnable run = { => d = i * 1.5; };
executor.submit(run);

甚至更多
executor.submit({ => d = i * 1.5; });

或者如果闭包支持代码块。
executor.submit() {
    d = i * 1.5; 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接