Scala 你猜我是怎麼理解閉包?

什麼是閉包

閉包 就是一個函數和與其相關的引用環境組成的一個整體

直白點說,閉包就是一個函數用了它作用域之外的變量( 單純理解的話理解到這裏就夠了,至於到底怎麼做到用作用域之外的變量,就是語法的問題了 ),就像下面的例子

//閉包基礎例子
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
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章