Scala方法和函数

Scala函数和方法描述

由于Scala是一门“混血”的语言,它同时兼具了函数和方法,方法是使用def关键字来进行定义的,如下代码片段所示:

scala> def addOneMethod(num: Int) = num + 1
addOneMethod: (num: Int)Int

可以通过使用Scala的匿名函数语法来创建函数并为其命名,同时将生成的函数分配给val变量,如以下代码所示:

scala> val addOneFunction = (num: Int) => num + 1
addOneFunction: Int => Int = <function1>

我们几乎总是能将方法作为高阶函数使用,举个例子,此处我们将方法和函数两个版本的addOne*()都传给了map()

scala> val someInts=List(1, 2, 3)
someInts: List[Int] = List(1, 2, 3)

scala> someInts map addOneMethod
res0: List[Int] = List(2, 3, 4)

scala> someInts map addOneFunction
res1: List[Int] = List(2, 3, 4)

由于方法的定义具有更加清晰的语法,所以每当我们需要定义函数时,都会使用定义方法的语法,而不是定义函数的语法,每当我们需要将一个方法手动转换成一个函数时,可以通过使用下划线操作符来达到目的,如下所示:

scala> addOneMethod _
res2: Int => Int = <function1>

Scala函数和方法区别

  • 方法可以作为一个表达式的一部分出现(调用函数并传参),但是方法(带参方法)不能作为最终的表达式,但是函数可以作为最终的表达式出现
//定义一个方法
scala> def method(x: Int) = 2 * x
method: (x: Int)Int

//定义一个函数
scala> val f = (x: Int) => 2 * x
f: Int => Int = <function1>

//方法不能作为最终表达式出现
scala> method
<console>:9: error: missing arguments for method method;
follow this method with `_' if you want to treat it as a partially applied function
              method
              ^

//函数可以作为最终表达式出现
scala> f
res4: Int => Int = <function1>

//无参方法可以作为最终表达式出现,其实这属于方法调用,Scala规定无参函数的调用可以省略括号
scala> def m1()=1+2
m1: ()Int

scala> m1
res10: Int = 3
  • 参数列表对于方法是可选的,但是对于函数是强制的(方法可以没有参数列表,参数列表也可以为空,但是函数必须有参数列表(也可以为空))
//方法可以没有参数列表
scala> def method2 = 100
mehtod2: Int

//方法可以有一个空得参数列表
scala> def method3() = 200
method3: ()Int

//函数必须有参数列表,否则报错
scala> val function1 = => 100
<console>:1: error: illegal start of simple expression
       val function1 = => 100
                       ^
                       
//函数也可以有一个空得参数列表
scala> val function2 = () =>100
function2: () => Int = <function0>

那么方法为什么可以没有参数列表呢,继续观看后续

  • 方法名意味着方法调用,函数名只是代表函数自身

因为方法不能作为最终的表达式存在,所以如果你写了一个方法的名字并且该方法不带参数(没有参数列表或者无参) 该表达式的意思是:调用该方法得到最终的表达式。因为函数可以作为最终表达式出现,如果你写下函数的名字,函 调用并不会发生,该方法自身将作为最终的表达式进行返回,如果要强制调用一个函数,你必须在函数名后面写()

//该方法没有参数列表
cala> method2
res7: Int = 100

//该方法有一个空的参数列表
scala> method3
res8: Int = 200

//得到函数自身,不会发生函数调用
scala> function2
res9: () => Int = <function0>

//调用函数
scala> function2()
res10: Int = 100
  • 为什么在函数出现的地方我们可以提供一个方法

在scala中很多高级函数,如map(),filter()等,都是要求提供一个函数作为参数。但是为什么我们可以提供一个方法呢 ?就像下面这样:

scala> val myList = List(1,2,3,5,6)
myList: List[Int] = List(1, 2, 3, 5, 6)

//map()参数是一个函数
scala> myList.map((x) => 2 * x)
res11: List[Int] = List(2, 4, 6, 10, 12)

//尝试给map()函数提供一个方法作为参数
scala> def method4(x: Int) = 3 * x
method4: (x: Int)Int

//正常执行
scala> myList.map(method4)
res12: List[Int] = List(3, 6, 9, 15, 18)

这是因为,如果期望出现函数的地方我们提供了一个方法的话,该方法就会自动被转换成函数。该行为被称为ETA expansion。

这样的话使用函数将会变得简单很多。你可以按照下面的代码验证该行为:

//期望出现函数的地方,我们可以使用方法
scala> val function3:(Int) => Int = method4
function3: Int => Int = <function1>

//不期望出现函数的地方,方法并不会自动转换成函数
scala> val v3 = method4
<console>:8: error: missing arguments for method method4;
follow this method with `_' if you want to treat it as a partially applied function
       val v3 = method4
                ^

利用这种自动转换,我们可以写出很简洁的代码,如下所示

//10.<被解释成obj.method,即整形的<的方法,所以该表达式是一个方法,会被解释成函数
scala> myList.filter(3.<)
res13: List[Int] = List(5, 6)

因为在Scala中操作符被解释称方法

  • 前缀操作符:op obj 被解释称obj.op
  • 前缀操作符:op obj 被解释称obj.op
  • 后缀操作符:obj op被解释称obj.op

你可以写成3,而不是3.<

scala> myList.filter(3<)
warning: there was one feature warning; re-run with -feature for details
res14: List[Int] = List(5, 6)

如何强制把衣服方法变成函数

可以在方法名后面加一个下划线强制变成函数,部分应用函数

scala> val function4 = method4 _
function4: Int => Int = <function1>

scala> function4(3)
res15: Int = 9

传名参数是一个方法

传名参数实质是一个没有参数列表的方法。正是因此你才可以使用名字调用而不用添加()

//使用两次‘x‘,意味着进行了两次方法调用
scala> def method8(x: => Int) = List(x,x)
method8: (x: => Int)List[Int]

scala> import util.Random
import util.Random

scala> val random=new Random()
random: scala.util.Random = scala.util.Random@65d8446c

//因为方法被调用了两次,所以两个值不相等
scala> method8(random.nextInt)
res17: List[Int] = List(18054652, -300793109)

如果你在方法体部分缓存了传名参数(函数),那么你就缓存了值(因为x函数被调用了一次)

//把传名参数代表的函数缓存起来
scala> def method9(x: => Int) = {val y = x; List(y, y)}
method9: (x: => Int)List[Int]

scala> method9(random.nextInt)
res19: List[Int] = List(332809003, 332809003)

能否在函数体部分引用传名参数所代表的方法呢,是可以的(缓存的是传名参数所代表的方法)

scala> def method10(x: => Int) = {val y = x _;List(y(), y())}
method10: (x: => Int)List[Int]

scala> method10(random.nextInt)
res20: List[Int] = List(-217889, -951276473)

参考资料:Scala与Clojure函数式编程模式

----EOF-----

Categories: scala Tags: scala