天天看点

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
  }
}
           
Scala 你猜我是怎么理解闭包?

眉头一皱,发现事情并不简单。不急我们慢慢来看

首先,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
  }
}