什麼是閉包
閉包 就是一個函數和與其相關的引用環境組成的一個整體
直白點說,閉包就是一個函數用了它作用域之外的變量( 單純理解的話理解到這裏就夠了,至於到底怎麼做到用作用域之外的變量,就是語法的問題了 ),就像下面的例子
//閉包基礎例子
object Closures {
var n: Int=10
def add10(i: Int): Int= {
//講道理這個{}括起來的代碼塊纔是add10的作用域
//這個n引用的就不是add10這個函數自己作用域內的變量
i+n
}
def main(args: Array[String]): Unit = {
println(add10(5))//15
}
}
還有兩種對閉包的形容我覺得十分貼切:
1. 閉包就是一個有記憶的函數
2. 閉包相當於一個只有一個方法的緊湊對象
爲什麼這麼說?
就像上面那個例子,函數add10似乎記住了變量n的值一樣。當然你可能覺得牽強,那麼我們換個方式實現一下閉包。
需求:寫一個計數器
需求很簡單,對於我們提筆就幹。
object Closures {
var count=0
def counter():Int={
count+=1
count
}
def main(args: Array[String]): Unit = {
println(counter())//1
println(counter())//2
println(counter())//3
}
}
腦子都不用動就能想出上面這樣的代碼,能很好的解決這個需求。但是你可能會想到,這和最開始那個例子有什麼不同嗎?而且缺點很明顯啊,中間隨便一個另外的函數都能破壞這個計數器的計數功能。
object Closures {
var count=0
def counter():Int={
count+=1
count
}
def trubleMaker():Int={
count+=1
count
}
def main(args: Array[String]): Unit = {
println(counter())//1
println(counter())//2
trubleMaker() //如果中間發生任何意外,計數器功能就失敗了
println(counter())//4
}
}
動動我們聰明的小腦瓜,想着把這個count變量放在counter()裏面不就可以了嗎?變成它自己的變量,只有它自己找得到。於是有了下面這樣的代碼
object Closures {
def counter():Int={
var count=0
count+=1
count
}
def main(args: Array[String]): Unit = {
println(counter())//1
println(counter())//1
println(counter())//1
}
}
不僅功能達不到需求了,而且閉包也不見了?不急,再改一下。
object Closures {
def Counter():()=>Int ={
var cnt: Int=0
def count(): Int ={
cnt=cnt+1
return cnt
}
return count
}
def main(args: Array[String]): Unit = {
var c=Counter()
println(c())//1
println(c())//2
println(c())//3
}
}
眉頭一皺,發現事情並不簡單。不急我們慢慢來看
首先,Counter返回的不再是一個Int而是一個函數了
再看,返回的函數count,每次拿Counter的cnt變量,然後+1再返回
最後把這個count函數返回
mian函數中用的時候,先拿到count()這個函數,調用count()達到了計數器的效果
這裏整個Counter函數內部構成了一個閉包,記住了cnt這個變量的值。
至於爲什麼說“閉包相當於一個只有一個方法的緊湊對象”這個形容得也很貼切呢。看看下面這個例子
object Closures {
def Counter():()=>Int ={
var cnt: Int=0 //對象的屬性 可以有多個
def count(): Int ={ //對象唯一的方法
cnt=cnt+1
return cnt
}
return count
}
def main(args: Array[String]): Unit = {
var c=Counter()
println("c--->"+c())//1
println("c--->"+c())//2
println("c--->"+c())//3
var d=Counter()
println("d--->"+d())//1
println("d--->"+d())//2
println("d--->"+d())//3
println("c--->"+c())//4
println("c--->"+c())//5
println("c--->"+c())//5
}
}
有什麼好處
函數有了記憶功能(閉包),就產生了一些美妙的東西。這就是閉包的好處,下面體會一下。
需求:
我們現在有一堆文件
比如a.avi b c.avi d
有的有後綴,有的沒有。我們需要給沒有後綴名的文件加上後綴
一樣,腦瓜子不用動,我們寫出了下面的代碼
object Closures {
//普通寫法
def makeSuffix(fileName: String, suffix: String): String = {
if (!fileName.endsWith(suffix)) {
return fileName.concat(suffix)
}
fileName
}
def main(args: Array[String]): Unit = {
println(makeSuffix("xiaocang",".avi"))//xiaocang.avi
println(makeSuffix("xiaoji.avi",".avi"))//xiaoji.avi
}
}
這樣其實 已經夠了,但是不夠完美,我們每次調用都要輸入一次後綴,用了閉包,我們只需要一開始確定後綴,讓函數記住它,更加方便。
object Closures {
//閉包寫法
def makeSuffixClos(suffix: String): (String) => String = {
def makeSuffix(fileName: String): String = {
if (!fileName.endsWith(suffix)) {
return fileName.concat(suffix)
}
fileName
}
return makeSuffix
}
def main(args: Array[String]): Unit = {
val f = makeSuffixClos(".avi")
println(f("xiaocang"))//xiaocang.avi
println(f("xiaoji.avi"))//xiaoji.avi
}
}
再變一下(這裏主要體現一下,其實柯里化也用到了閉包)
object Closures {
//柯里化寫法
def makeSuffixCurrying(suffix: String)(fileName: String): String = {
if (!fileName.endsWith(suffix)) {
return fileName.concat(suffix)
}
fileName
}
def main(args: Array[String]): Unit = {
val f = makeSuffixCurrying(".avi") _ //這裏轉成函數一下
println(f("xiaocang")) //xiaocang.avi
println(f("xiaoji.avi")) //xiaoji.avi
}
}