java8 闭包_Java 8 Lambda限制:闭包

摘要:本文将主要讨论一下java8 lambda的一些限制和闭包的概念,以及简单比较一下java和javascripe实现闭包的区别。

3cdb222634101ea63c45c28ed36f5a8d.png

假设我们想创建一个简单的线程,只在控制台上打印一些东西:

int answer = 42;

Thread t = new Thread(

() -> System.out.println("The answer is: " + answer)

);

如果我们想在线程里面修改

的值怎么办?

在本文中,我想回答这个问题,讨论Java lambda表达式的限制和沿途的后果。

简单的答案是Java实现闭包,但是当我们将它们与其他语言进行比较时会有限制。另一方面,这些限制可以被认为是可忽略的。

为了支持这种说法,我将展示闭包在JavaScript这一著名语言中起着至关重要的作用。

Java 8 Lambda表达式从哪里来?

在过去,实现上述示例的紧凑方法是创建一个新的

匿名类的实例,如下所示:

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表达式的实现之间存在差异。

但是,主要的一点我想在此强调的是,考虑到他们在封闭范围如何交互,我们可以认为它们只是一种创建匿名类接口的紧凑方式,比如Runnable , Callable, Function, Predicate ,等。实际上,lambda表达式和它的封闭范围之间的相互作用保持完全相同(即 this 关键字语义上的差异 )。

Java 8 Lambda限制

Java中的lambda表达式(以及匿名类)只能访问封闭范围的最终(或实际上最终)变量。

例如,考虑以下示例:

void fn() {

int myVar = 42;

Supplier lambdaFun = () -> myVar; // error

myVar++;

System.out.println(lambdaFun.get());

}

这不会编译,因为增量 myVar 阻止它是实际上最终变量。

JavaScript及其功能

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创建闭包?

Java只保存自由变量的值,让它们在lambda表达式中使用。即使有一个增量myVar,lambda函数仍然会返回42.编译器避免了那些不相干的情况的创建,限制可以在lambda表达式(和匿名类)内部使用的变量的类型只有最终的和实际上最终的。

尽管有这个限制,我们可以使用Java 8实现闭包。事实上,闭包更多的是理论上的理解,只捕获自由变量的价值。在纯函数语言中,这应该是唯一允许的,保持引用透明度属性。

后来,一些功能语言以及诸如Javascript之类的语言引入了捕获自由变量的存储位置的可能性。这允许引入副作用的可能性。

所以,我们可以说,使用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

现在让我们尝试在Java中做同样的事情:

public static Map createCounter(int initValue) { // the enclosing scope

int count = initValue;

Map map = new HashMap<>();

map.put("val", () -> count);

map.put("inc", () -> count++);

return map;

}

此代码不会编译,因为第二个lambda函数试图更改变量count。

Java将函数变量(例如count)存储在堆栈中; 那些被删除与终止createCounter。创建的lambdas使用的复制版本count。如果编译器允许第二个lambda改变它的复制版本count ,那将是很混乱。

Java闭包使用可变对象

正如我们所看到的,使用的变量的值被复制到lambda表达式(或匿名类)。但是,如果我们使用对象呢?在这种情况下,只有引用将被复制,我们可以看看有点不同的东西。

我们几乎可以用以下方式模拟JavaScript的闭包的行为:

private static class MyClosure {

public int value;

public MyClosure(int initValue) { this.value = initValue; }

}

public static Map createCounter(int initValue) {

MyClosure closure = new MyClosure(initValue);

Map 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/java-8-lambas-limitations-closures

译文首发:https://blog.maxleap.cn/archives/1297

相关文章

作者往期佳作

欢迎关注微信公众号:MaxLeapSvc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值