摘要: 主要讨论一下java8 lambda的一些限制和闭包的概念,以及简单比较一下java和javascripe实现闭包的区别。java
假设咱们想建立一个简单的线程,只在控制台上打印一些东西:编程
int answer = 42; Thread t = new Thread( () -> System.out.println("The answer is: " + answer) );
若是咱们想在线程里面修改answer
的值怎么办?后端
在本文中,我想回答这个问题,讨论Java lambda表达式的限制和沿途的后果。缓存
简单的答案是Java实现闭包,可是当咱们将它们与其余语言进行比较时会有限制。另外一方面,这些限制能够被认为是可忽略的。服务器
为了支持这种说法,我将展现闭包在JavaScript这一著名语言中起着相当重要的做用。微信
在过去,实现上述示例的紧凑方法是建立一个新的Runnable
匿名类的实例,以下所示:闭包
int answer = 42; Thread t = new Thread(new Runnable() { public void run() { System.out.println("The answer is: " + answer); } });
从Java 8开始,上一个例子可使用lambda表达式编写。函数
如今,咱们都知道Java 8 lambda表达式不只仅是为了下降代码的冗长性,他们还有不少其余的新功能。此外,在匿名类和lambda表达式的实现之间存在差别。this
可是,主要的一点我想在此强调的是,考虑到他们在封闭范围如何交互,咱们能够认为它们只是一种建立匿名类接口的紧凑方式,好比 Runnable
, Callable
, Function
, Predicate
,等。实际上,lambda表达式和它的封闭范围之间的相互做用保持彻底相同(即this
关键字语义上的差别 )。spa
Java中的lambda表达式(以及匿名类)只能访问封闭范围的最终(或实际上最终)变量。
例如,考虑如下示例:
void fn() { int myVar = 42; Supplier<Integer> lambdaFun = () -> myVar; // error myVar++; System.out.println(lambdaFun.get()); }
这不会编译,由于增量myVar
阻止它是实际上最终变量。
JavaScript中的函数和lambda表达式使用闭包的概念:
“闭包是一种特殊类型的对象,它结合了两个东西:一个函数,以及建立该函数的环境。环境包括在建立闭包时在范围内的任何局部变量” - MDN
事实上,前面的例子在JavaScript中工做得很好。
function fn() { // the enclosing scope var myVar = 42; var lambdaFun = () => myVar; myVar++; console.log(lambdaFun()); // it prints 43 }
此示例中的lambda函数使用的已更改值的myVar
。
实际上,在JavaScript中,一个新函数维护一个指向它所定义的封闭范围的指针。这个基本机制容许建立闭包,这保存了自由变量的存储位置 - 这些能够由函数自己以及其余函数修改。
Java只保存自由变量的值,让它们在lambda表达式中使用。即便有一个增量myVar
,lambda函数仍然会返回42.编译器避免了那些不相干的状况的建立,限制能够在lambda表达式(和匿名类)内部使用的变量的类型只有最终的和实际上最终的。
尽管有这个限制,咱们可使用Java 8实现闭包。事实上,闭包更多的是理论上的理解,只捕获自由变量的价值。在纯函数语言中,这应该是惟一容许的,保持引用透明度属性。
后来,一些功能语言以及诸如Javascript之类的语言引入了捕获自由变量的存储位置的可能性。这容许引入反作用的可能性。
因此,咱们能够说,使用JavaScript的闭包,咱们能够作更多。可是,这些反作用如何真正帮助JavaScript?他们真的很重要吗?
为了更好地理解闭包的概念,如今考虑下面的JavaScript代码(forgive在JavaScript中,这能够以很是紧凑的方式完成,但我想它看起来像Java并进行比较):
function createCounter(initValue) { // the enclosing scope var count = initValue; var map = new Map(); map.set('val', () => count); map.set('inc', () => count++); return map; } v = createCounter(42); v.get('val')(); // returns 42 v.get('inc')(); // returns 42 v.get('val')(); // returns 43
每次 createCounter
调用时,它都会建立一个具备两个新lambda函数的映射,它们分别返回和递增在封闭范围中定义的变量值。
换句话说,第一个函数具备改变另外一个函数的结果的反作用。
这里要注意的一个重要事实是,它createCounter的做用域在它的终止以后仍然存在,而且同时被两个lambda函数使用。
如今让咱们尝试在Java中作一样的事情:
public static Map<String, Supplier> createCounter(int initValue) { // the enclosing scope int count = initValue; Map<String, Supplier> map = new HashMap<>(); map.put("val", () -> count); map.put("inc", () -> count++); return map; }
此代码不会编译,由于第二个lambda函数试图更改变量count
。
Java将函数变量(例如count
)存储在堆栈中; 那些被删除与终止createCounter
。建立的lambdas使用的复制版本count
。若是编译器容许第二个lambda改变它的复制版本count
,那将是很混乱。
正如咱们所看到的,使用的变量的值被复制到lambda表达式(或匿名类)。可是,若是咱们使用对象呢?在这种状况下,只有引用将被复制,咱们能够看看有点不一样的东西。
咱们几乎能够用如下方式模拟JavaScript的闭包的行为:
private static class MyClosure { public int value; public MyClosure(int initValue) { this.value = initValue; } } public static Map<String, Supplier> createCounter(int initValue) { MyClosure closure = new MyClosure(initValue); Map<String, Supplier> counter = new HashMap<>(); counter.put("val", () -> closure.value); counter.put("inc", () -> closure.value++); return counter; } Supplier[] v = createCounter(42); v.get("val").get(); // returns 42 v.get("inc").get(); // returns 42 v.get("val").get(); // returns 43
事实上,这不是真正有用的东西,它真的是不太优雅。
JavaScript使用闭包做为建立“类”实例:对象的基本机制。这就是为何在JavaScript中,相似的函数MyCounter
称为“构造函数”。
相反,Java已经有类,咱们能够以更优雅的方式建立对象。
在前面的例子中,咱们不须要一个闭包。“工厂函数”本质上是一个类定义的奇怪的例子。在Java中,咱们能够简单地定义一个类,以下所示:
class MyJavaCounter { private int value; public MyJavaCounter(int initValue) { this.value = initValue; } public int increment() { return value++; } public int get() { return value; } } MyJavaCounter v = new MyJavaCounter(42); System.out.println(v.get()); // returns 42 System.out.println(v.increment()); // returns 42 System.out.println(v.get()); // returns 43
修改自由变量(即在lambda函数以外定义的任何对象)的Lambda函数可能会产生混淆。其余功能的反作用可能会致使没必要要的错误。
这是典型的老年语言的开发人员不明白为何JavaScript产生随机的莫名其妙的行为。 在功能语言中,它一般是有限的,而当它不是,则不鼓励。
考虑你正在使用并行范例,例如在Spark中:
int counter = 0; JavaRDD rdd = sc.parallelize(data); rdd.foreach(x -> counter += x); // Don't do this!!
咱们已经看到了一个很是简要的Java 8 lambda表达式。咱们专一于匿名类和lambda表达式之间的区别。以后,咱们更好地看到了闭包的概念,看看它们是如何在JavaScript中实现的。此外,咱们看到JavaScript的闭包不能直接在Java 8中使用,以及如何经过对象引用来模拟它。
咱们还发现,当咱们将它们与JavaScript之类的语言进行比较时,Java对闭包的支持有限。
然而,咱们看到这些限制并不真正重要。事实上,闭包在JavaScript中被用做定义类和建立对象的基本机制,咱们都知道它不是Java问题。
做者信息
本文系力谱宿云LeapCloud旗下MaxLeap团队_Service&Infra成员:贾威威 【翻译】
从过后端开发已有多年,目前主要负责MaxWon服务端部分功能的开发与设计。
原文连接:https://dzone.com/articles/ja...
做者往期佳做
使用Vert.x构建Web服务器和消息系统
集中式内存缓存Guava Cache
欢迎关注微信公众号