Scala 编程—第五节:函数与闭包

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HG_Harvey/article/details/82226239

1.函数定义

如下,定义一个函数,用来比较两数大小得出最大值

def max(x: Int, y: Int): Int = {
  if (x> y) x
  else y
}

以上述定义的函数为例,看下scala函数的基本构成
这里写图片描述

max 函数可以简写为如下

def max(x: Int, y: Int) = if (x > y) x else y

函数调用

println(max(3, 5)) // 运行结果:5

2.方法定义

Scala 有方法与函数,二者在语义上的区别很小,Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量,换言之就是在类中定义的函数即是方法。

如下,定义了两个方法,用于读取指定文件并打印输出所有长度超过设定值得行

package com.harvey

import scala.io.Source

object LongLines {

  def processFile(filename : String, width : Int): Unit = {
    val source = Source.fromFile(filename)
    for (line <- source.getLines) {
      processLine(filename, width, line)
    }
  }

  private def processLine(filename : String, width : Int, line : String): Unit = {
    if (line.length > width) {
      println(filename + ":" + line.trim)
    }
  }
}

main方法调用执行

package com.harvey

object FindLongLines {
  def main(args: Array[String]): Unit = {
    val width = 45;
    val filename = "D:\\code\\study\\scalademo\\src\\main\\java\\com\\harvey\\LongLines.scala"
    LongLines.processFile(filename, width)
  }
}

运行结果:

D:\code\study\scalademo\src\main\java\com\harvey\LongLines.scala:def processFile(filename : String, width : Int): Unit = {
D:\code\study\scalademo\src\main\java\com\harvey\LongLines.scala:private def processLine(filename : String, width : Int, line : String): Unit = {

3.本地函数

上述例子中,使用的private修饰了processLine方法,使之成为私有方法(类比java中的私有方法),只能在LongLines 类中使用,Scala中还提供了另一种方式,可以把函数定义在别的函数之内,即嵌套函数,如果本地变量一样,修改代码如下

def processFile(filename: String, width: Int): Unit = {
  def processLine(filename: String, width: Int, line: String): Unit = {
    if(line.length > width) {
      println(filename + ": " + line)
    }
  }
  val source = Source.fromFile(filename)
  for (line <- source.getLines){
    processLine(filename, width, line)
  }
}

运行结果同上

4.头等函数

Scala的函数是头等函数,不仅可以定义和调用函数,还可以写成匿名的字面量,并作为值传递。
对数执行递增操作的函数字面量例子:

(x: Int) => x + 1

=>指明这个函数把左边的东西(任意整数x)转变成右边的东西(x + 1),即把任意整数映射成x + 1,简单理解就是=>左边是表示输入,右边表示转换操作

函数值就是对象也是函数,可以存入变量,也可以使用括号来调用

var incr = (x: Int) => x + 1
print(incr(10)) // 运行结果:11

如果想让函数字面量包含多条语句,可以使用花括号包住函数体,例如

var incr = (x: Int) => {
  print("result = ")
  x + 1
}
print(incr(10)) // 运行结果:result = 11

5.函数字面量的短格式

Scala 提供了许多方法来取出冗余信息并把函数字面量写的更简短,例如

var someNumbers = List(-1, -3, 2, 4, 9, -9)
var result = someNumbers.filter((x: Int) => x > 0)
print(result) // 运行结果:List(2, 4, 9)

可以将filter中的函数进行简写,去掉类型

var result = someNumbers.filter(x => x > 0)

6.占位符语法

如果想让函数字面量更简洁,可以把下划线当做一个或更多参数的占位符,只要每个参数在函数字面量中出现一次,比如,_>0 检查值是否大于零

var result = someNumbers.filter(_> 0)

可以把_看作表达式需要被“填入”的“空白”。这个空白在每次函数调用的时候用函数的参数填入。例如
someNumbers初始化值为List(-1, -3, 2, 4, 9, -9),filter(_>0),里面的_首先使用-1替换,即-1>0,然后再用-3替换,依次类推。

需要注意,有时把下划线当作参数的占位符,编译器可能无法推断缺失的参数类型,例如

val f = _ + _

会报如下错误

missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
    val f = _ + _

这种情况下,使用冒号指定类型即可

val f = (_: Int) + (_: Int)
print(f(2, 8)) // 运行结果:10

7.部分应用函数

下划线不仅能替换单个参数,还可以替换整个参数列表,例如:写成println()或println。如下

someNumbers.foreach(println(_))

Scala把这种简短格式直接看作你输入了如下代码:

someNumbers.foreach(x => println(x))

上面例子中的下划线不是单个参数的占位符,而是整个参数列表的占位符,以这种方式使用下划线时,其实就是一个部分应用函数。我们再看个例子,如下

def sum(a: Int, b: Int, c: Int) = a + b + c
print(sum(1, 2, 3)) // 运行结果:6

部分应用函数是一种表达式,不需要函数所需要的所有参数,比如:要创建调用sum的部分应用表达式,而不提供任何3个所需参数,只要在“sum”之后加上下划线,然后可以把得到的函数存入变量

var a = sum _
print(a(1, 2, 3)) // 运行结果:6

实际发生的事情是这样的:名为a的 变量只是一个函数值对象。这个函数值由scala编译器依照部分应用函数表达式sum _,自动产生类的实例。编译器产生的类有一个apply方法带3个参数。之所以带3个参数是因为sum _表达式缺少的参数数量为3,Scala编译器把表达式a(1, 2, 3)翻译成对函数值的apply方法的调用,传入3个参数1、2、3。因此a(1, 2, 3)是下列代码的短格式

a.apply(1, 2, 3)

8.闭包

在理解闭包是什么意思前,我们先来看个例子

val addMore = (x: Int) => x + more
print(addMore(10))

执行报错:

Error:(37, 35) not found: value more
    val addMore = (x: Int) => x + more

x变量是一个绑定变量,它在函数的上下文有明确意义,被定义函数的唯一参数是Int,值函数调用的时候被赋值,称这种变量为绑定变量(Bound Variable)。more是一个没有给定含义的不定变量,这里的more是一个自由变量(Free Variable)。所以在使用的时候,编译器会报错。改写上述代码如下

var more = 1
val addMore = (x: Int) => x + more
print(addMore(10)) // 运行结果:11

像这种运行时确定more类型及值的函数称为闭包,more是个自由变量,在运行时其值和类型得以确定
这是一个由开放(free)到封闭的过程,因此称为闭包。

9.重复参数

Scala中,可以指明函数的最后一个参数是重复的,即可变参数,类似java中动态参数。标注一个重复参数,在参数类型后面加一个星号

scala> def echo(args: String*) = for(arg <- args) println(arg)
echo: (args: String*)Unit
scala> echo()
scala> echo("hello")
hello
scala> echo("hello", "scala")
hello
scala

可能你会想到,在传入多个参数的时候,把参数封装为一个Array,我们来看下,是否可以

val arr = Array("hello", "scala")
echo(arr)

运行报错:

Error:(51, 10) type mismatch;
 found   : Array[String]
 required: String
    echo(arr)

函数内部,重复参数的类型是声明参数类型的数组,想要实现将数组中的每个元素当做参数,而不是当做单一的参数传给echo,在数组参数后面添加一个冒号和_*符号,如下

scala> echo(arr: _*)
hello
scala

猜你喜欢

转载自blog.csdn.net/HG_Harvey/article/details/82226239