1.闭包的概念
- 闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。--百度百科
第一句总结的很简洁了:闭包就是能够读取其他函数内部变量的函数。(这句话背会,要考!)
只说概念不好理解,下面上例子:
1.1 Java中闭包的实现(伪闭包?)
public class ClosureTest {
interface Play {
void playGame();
}
final private String hour = "1";//被内部类访问的变量,必须为 final
public void test1(final String h) {
//成员内部类
Play p = new Play() {
@Override
public void playGame() {
System.out.println("playGame" + h + "小时");//在这里访问了传入的外部变量h
}
};
p.playGame();
}
//测试写法
public void main() {
test1(hour);//在这里将外部变量hour传了进去
}
}
从上面代码可以看到,内部类Play访问了ClosureTest 类的成员变量hour。这就是闭包在java中的一种简单实现。(至于为什么要用final字段,下文有详细说明)
1.1.1 java中闭包的作用
OK,实现看完了,那这么做有什么用呢:
- 核心作用:将变量生命周期拉长了。
解释:在上例中hour字段本身是ClosureTest的内部变量,生命周期是与ClosureTest绑定的。 但是内部类Play引用了该字段,因此hour的生命周期被拉长到Play执行结束。
- 因为是用内部类实现的,所以当然也就拥有了内部类的功用。
内部类最重要的意义之一便是:间接让java拥有多继承的能力。至于内部类的具体功用,不是本篇重点,便不再赘述。
1.1.2 为什么要用final
上文还留有一个疑问:为什么一定要用final字段修饰变量呢?
这个就涉及到java老生常谈的一个问题了:多线程问题。
想象一下:当线程1被销毁之后,线程1中的变量也随着销毁了。那么这时如果线程2引用了线程1的变量是不是就出问题了呢??这就是java中被内部类引用的外部变量需要用final修饰的原因。
那么,问题又来了:为什么用final修饰变量就可以避免这个问题呢?
(以下内容部分引自 https://www.jianshu.com/p/f55b11a4cec2)
这里只放结论:在java中,当主方法结束时,局部变量会被cleaned up 而内部类可能还在运行。当局部变量声明为final时,当使用已被cleaned up的局部变量时会把局部变量替换成常量。
解释:在java中,如果局部变量被声明称final。在使用时,如果该变量被销毁。在使用该变量的地方会复制一份该变量的值存储起来并使用。又因为是final,所以该值不会变,也就等同于使用了被销毁的变量。但是,这样的话其实在变量被内部类使用的那一刻他的值就是固定了的,无法修改且不受外部变量变化的影响。 这也是java闭包被称为伪闭包的原因。
1.2 Kotlin中的闭包
想要理解kotlin中闭包的实现,首先要懂kotlin中的一个概念:在Kotlin中,函数是“一等公民”。
对比一下java和kotlin更好理解:
java代码:
public class TestJava{
private void test(){
private void test(){//错误,因为Java中不支持函数包含函数
}
}
}
在java中是不支持这种写法的,因为函数是“二等公民”。
下面再看下kotlin代码:
fun test1(){
fun test2(){//正确,因为Kotlin中函数是一等公民,函数可以嵌套函数,甚至函数可以作为返回值或者参数进行传递
}
}
是不是大概明白了?如果还是不太懂的小伙伴可以先了解下函数式编程。推荐这篇文章:http://www.ruanyifeng.com/blog/2012/04/functional_programming.html 这位大神写的非常简单易懂,在我学习函数式编程时这篇文章给了我极大的帮助。再此表示感谢!
大家可能发现了,既然kotlin可以函数嵌套函数。那么就完全不用java那一套内部类+接口去实现闭包了!!!
那么kotlin中的闭包实现完全可以这么写:
fun test(): () -> Unit {
var a = 0
return fun() {
a++
println(a)
}
}
fun main() {
val t = test()
println(t())
println(t())
println(t())
}
是不是发现了新世界的大门,内部函数很轻松地调用了外部变量a
这只是一个最简单的闭包实现。按照这种思想,其他的实现例如:函数、条件语句、Lambda表达式等等都可以理解为闭包,这里不再赘述。不过万变不离其宗,只要记得一句话:闭包就是能够读取其他函数内部变量的函数
1.3 总结和思考
看到这里,大家是不是对闭包有了简单的理解了呢?这里再次强调一下概念:闭包就是能够读取其他函数内部变量的函数。
关于闭包的概念只需要记住这一句话,千万别搞混了。因为很多语言中都有闭包这个词,但是说法却又不尽相同。盲目去看的话会导致大家越看越混乱。
强行背概念记忆总是不够深刻。不知道有没有小伙伴有这种疑惑:闭包这个词为什么是 “能够读取其他函数内部变量的函数”。虽然我的英语也不咋地,但是我也知道Closure这个单词有关闭、终止的意思啊~~ 可是再看上面那句话,哪里关闭了?哪里终止了?好像和“闭”这个词完全没有半毛钱关系啊??如果你也有以上疑惑,请继续往下看。
2.“闭包”一词的由来
这一章的灵感完全来自于这边文章:https://www.jianshu.com/p/c22db2a91989 作者详细讲述了闭包的概念和由来,想要深入了解的同学可以直接看这篇文章。下面我引用并简单解释下“闭包”一词的由来。
lambda表达式的闭包是定义在外部上下文(环境)中特定的符号集,它们给这个表达式中的自由符号赋值。它将一个开放的、仍然包含一些未定义的符号lambda表达式变为一个关闭的 lambda表达式,使这个lambda表达式不再具有任何自由符号。
OK,关键字关闭出来了。具体细节大家可以通读一下上面那篇文章。
哦了,本篇到此为止。有疑问或者建议欢迎大家提出~~~~