第三章 (4)扩展------lambda表达式与闭包(关于lambda使用局部变量的补充)

      关于闭包,掌握js的童鞋会更加的了解,但是如今,我们在学习java8的lambda的时候,上一章提到lambda关于使用局部变量的时候,书中提到了lambda与闭包的问题。他的原话是这么说的:

     你可能已经听说过闭包(closure,不要和Clojure编程语言混淆)这个词,你可能会想Lambda是否满足闭包的定义。用科学的说法来说,闭包就是一个函数的实例,且它可以无限制地访问那个函数的非本地变量。例如,闭包可以作为参数传递给另一个函数。它也可以访问和修改其作用域之外的变量。现在,Java 8的Lambda和匿名类可以做类似于闭包的事情:它们可以作为参数传递给方法,并且可以访问其作用域之外的变量。但有一个限制:它们不能修改定义Lambda的方法的局部变量的内容。这些变量必须是隐式最终的。可以认为Lambda是对值封闭,而不是对变量封闭,如前所述,这种限制存在的原因在于局部变量保存在栈上,并且隐式表示它们仅限于其所在线程。如果允许捕获可改变的局部变量,就会引发造成线程不安全的新的可能性,而这是我们不想看到的(实例变量可以,因为它们保存在堆中,而堆是在线程之间共享的)。

     无奈本人现在才疏学浅,语文水平也仅限于读一读中学语文课本的程度,不能很好的理解这段话的含义。只能通过查阅一下资料,了解一下关于闭包与lambda。

    

      闭包是个什么东西呢?闭包就是把函数以及变量包起来,使得变量的生存周期延长。闭包跟面向对象是一棵树上的两条枝,实现的功能是等价的。

     这样说可能不够直观,我们还是用代码说话吧。其实Java在很早的版本就支持闭包了,只是因为应用场景太少,这个概念一直没得到推广。在Java6里,我们可以这样写:

先定义一个接口:

public interface Supplier<T> {

	T get();
}

在另一个类的方法中返回该接口的实现内部类,并使get方法返回方法中的局部变量:

public class Bibao {

	public static Supplier<Integer> testClosure(){
		  final int i = 4;
		 return new Supplier<Integer>() {

			public Integer get() {
				// TODO Auto-generated method stub
				return i;
			}
		
		};
	}
}

      看出问题了么?这里i是函数testClosure的内部变量,但是最终返回里的匿名对象里,仍然返回了i。我们知道,函数的局部变量,其作用域仅限于函数内部,在函数结束时,就应该是不可见状态,而闭包则将i的生存周期延长了,并且使得变量可以被外部函数所引用。这就是闭包了。这里,其实我们的lambda表达式还没有出现呢!

    而支持lambda表达式的语言,一般也会附带着支持闭包了,因为lambda总归在函数内部,与函数局部变量属于同一语句块,如果不让它引用局部变量,不会让人很别扭么?

Java中闭包带来的问题

在Java的经典著作《Effective Java》、《Java Concurrency in Practice》里,大神们都提到:匿名函数里的变量引用,也叫做变量引用泄露,会导致线程安全问题,因此在Java8之前,如果在匿名类内部引用函数局部变量,必须将其声明为final,即不可变对象。(Python和Javascript从一开始就是为单线程而生的语言,一般也不会考虑这样的问题,所以它的外部变量是可以任意修改的)。

扫描二维码关注公众号,回复: 3905455 查看本文章

在Java8里,有了一些改动,现在我们可以这样写lambda或者匿名类了:

1

2

3

4

5

6

public static Supplier<Integer> testClosure() {

 int i = 1;

 return () -> {

 return i;

 };

}

这里我们不用写final了!但是,Java大神们说的引用泄露怎么办呢?其实呢,本质没有变,只是Java8这里加了一个语法糖:在lambda表达式以及匿名类内部,如果引用某局部变量,则直接将其视为final。我们直接看一段代码吧:

1

2

3

4

5

6

7

public static Supplier<Integer> testClosure() {

 int i = 1;

 i++;

 return () -> {

 return i; //这里会出现编译错误

 };

}

明白了么?其实这里我们仅仅是省去了变量的final定义,这里i会强制被理解成final类型。很搞笑的是编译错误出现在lambda表达式内部引用i的地方,而不是改变变量值的i++…这也是Java的lambda的一个被人诟病的地方。我只能说,强制闭包里变量必须为final,出于严谨性我还可以接受,但是这个语法糖有点酸酸的感觉,还不如强制写final呢…

猜你喜欢

转载自blog.csdn.net/qq564425/article/details/81369284