Scala函数和方法的区别浅析
“Scala函数和方法的区别。”
1. 函数和方法
在Scala中函数是一等公民,你可以像操作数字一样将函数赋值给一个变量。使用val语句可以定义函数,def语句定义方法:
class Test{
//方法def m(x: Int) = x + 3
//函数val f = (x: Int) => x + 3
val f1:(Int)=>Int = (y) => y + 3
}
在Scala中,无法直接操作方法,如果要操作方法,必须先将其转换成函数。有两种方法可以将方法转换成函数:
val f1 = m _
在方法名称m后面紧跟一个空格和下划线,告诉编译器将方法m转换成函数,而不是要调用这个方法。也可以显示地告诉编译器需要将方法转换成函数:
val f1: (Int) => Int = m
通常情况下编译器会自动将方法转换成函数,例如在一个应该传入函数参数的地方传入了一个方法,编译器会自动将传入的方法转换成函数。
2. 两者的区别
可以直接调用函数上的方法,而方法却不行,例如:
f.toString //编译通过
m.toString //编译失败
3. Currying函数和Currying方法
Currying函数可以只传入部分参数返回一个偏函数(partially applied function, 也叫部分应用函数),而Currying方法在转换成偏函数时需要加上显式说明,让编译器完成转换:
object TestCurrying {
def invoke(f: Int => Int => Int): Int = {
f(1)(2)
}
def multiply(x: Int)(y: Int): Int = x * y
def main(args: Array[String]) {
invoke(multiply)//编译器会自动将multiply方法转换成函数
val partial1 = multiply(1) //尝试将Currying方法转换成偏函数导致编译失败
val partial2 = multiply(1): (Int => Int) //编译通过
val f = multiply _ //将multiply方法转换成函数f
val partial3 = f(1) //只应用第1个参数返回偏函数,编译通过
}
}
Scala中的方法跟Java的方法一样,方法是组成类的一部分。方法有名字、类型签名,有时方法上还有注解,以及方法的功能
Scala中的函数是一个完整的对象。Scala中用22个特质(trait)抽象出了函数的概念。这22特质从Function1到Function22
如上图中的Function10代表的是:有10个形参,返回值为R(协变)的函数。
Scala中的函数其实就是继承了这些Trait的类的对象,如:我们通过函数字面量定义一个函数:
由于Function2是特质,不能直接new。上述new Function2[Int,Int,Int](){}其实是定义并实例化一个实现了Function2特质的类的对象。
apply是scala中的语法糖:对一个对象obj上调用obj(),scala编译器会转换为obj.apply();在一个类clazz上调用clazz(),scala编译器会转
换为clazz_company_obj.apply(),其中clazz_company_obj为clazz的伴生对象。
具体的差异,总结为如下几点:
1.方法不能作为单独的表达式而存在(参数为空的方法除外),而函数可以。如:
在如上的例子中,我们首先定义了一个方法m,接着有定义了一个函数f。接着我们把函数名(函数值)当作最终表达式来用,由于f本身就是
一个对象(实现了FunctionN特质的对象),所以这种使用方式是完全正确的。但是我们把方法名当成最终表达式来使用的话,就会出错。
2.函数必须要有参数列表,而方法可以没有参数列表
在如上的例子中,m1方法接受零个参数,所以可以省略参数列表。而函数不能省略参数列表
3.方法名是方法调用,而函数名只是代表函数对象本身
这个比较容易理解。因为保存函数字面量的变量(又称为函数名或者函数值)本身就是实现了FunctionN特质的类的对象,要调用对象的apply
方法,就需要使用obj()的语法。所以函数名后面加括号才是调用函数。如下:
4.在需要函数的地方,如果传递一个方法,会自动进行ETA展开(把方法转换为函数)
如果我们直接把一个方法赋值给变量会报错。如果我们指定变量的类型就是函数,那么就可以通过编译,如下:
当然我们也可以强制把一个方法转换给函数,这就用到了scala中的部分应用函数:
5.传名参数本质上是个方法
传名参数实质上是一个参数列表为空的方法,如下:
如上代码实际上定义了一个方法m1,m1的参数是个传名参数(方法)。由于对于参数为空的方法来说,方法名就是方法调用
,所以List(x,x)实际上是进行了两次方法调用。
由于List(x,x)是进行了两次方法调用,所以得到两个不同的值。
在Scala中函数和方法区别:
1、有参方法可以作为表达式的一部分出现,无参方法可以作为最终表达式出现;但函数可以作为最终表达式出现
方法可以作为一个表达式的一部分出现(调用函数并传参),但带参方法不能作为最终的表达式出现,无参方法可以作为最终表达式出现,其实这属于方法调用,scala规定无参方法的调用可以省略括号;但是函数可以作为最终的表达式出现:
scala> //定义一个方法
scala> def m(x:Int) = 2*x
m: (x: Int)Int
scala> //定义一个函数
scala> val f = (x:Int) => 2*x
f: Int => Int = <function1>
scala> //方法不能作为最终表达式出现
scala> m
<console>:9: error: missing arguments for method m;
follow this method with `_‘ if you want to treat it as a partially applied function
m
^
scala> //函数可以作为最终表达式出现
scala> f
res9: Int => Int = <function1>
scala> //定义无参方法,并可以作为最终表达式出现
scala> def m1()=1+2
m1: ()Int
scala> m1
res10: Int = 3
2、参数列表对于方法是可选的,但是对于函数参数列表是强制的
方法可以没有参数列表,参数列表也可以为空;而函数必须有参数列表(也可以为空)。
scala> //方法可以没有参数列表
scala> def m2 = 100;
m2: Int
scala> //方法可以有一个空的参数列表
scala> def m3() = 100
m3: ()Int
scala> //函数必须有参数列表,否则报错
scala> var f1 = => 100
<console>:1: error: illegal start of simple expression
var f1 = => 100
^
scala> //函数也可以有一个空的参数列表
scala> var f2 = () => 100
f2: () => Int = <function0>
那么方法为什么可以没有参数列表呢,往下看。
3、方法名意味着方法调用;但函数名只是代表函数自身。
因为方法不能作为最终的表达式存在,所以如果你写了一个方法的名字并且该方法不带参数(没有参数列表或者无参)
该表达式的意思是:调用该方法得到最终的表达式。因为函数可以作为最终表达式出现,如果你写下函数的名字,函数
调用并不会发生,该方法自身将作为最终的表达式进行返回,如果要强制调用一个函数,你必须在函数名后面写()
scala> //该方法没有参数列表
scala> m2
res11: Int = 100
scala> //该方法有一个空的参数列表
scala> m3
res12: Int = 100
scala> //得到函数自身,不会发生函数调用
scala> f2
res13: () => Int = <function0>
scala> //调用函数
scala> f2()
res14: Int = 100
为什么在函数出现的地方我们可以提供一个方法?
4、方法可以自动(称之ETA扩展)或手动强制转换为函数;但是函数不可以转换成方法
在期望出现函数的地方使用方法,该方法自动转换成函数;手动强制转换可以使用 "方法名 _" 转换成函数
在scala中很多高级函数,如map(),filter()等,都是要求提供一个函数作为参数。但是为什么我们可以提供一个方法呢?就像下面这样:
scala> val myList = List(3,56,1,4,72)
myList: List[Int] = List(3, 56, 1, 4, 72)
scala> // map()参数是一个函数
scala> myList.map((x) => 2*x)
res15: List[Int] = List(6, 112, 2, 8, 144)
scala> //尝试给map()函提供一个方法作为参数
scala> def m4(x:Int) = 3*x
m4: (x: Int)Int
scala> //正常执行
scala> myList.map(m4)
res17: List[Int] = List(9, 168, 3, 12, 216)
这是因为,如果期望出现函数的地方我们提供了一个方法的话,该方法就会自动被转换成函数。该行为被称为ETA expansion。
这样的话使用函数将会变得简单很多。你可以按照下面的代码验证该行为:
scala> //期望出现函数的地方,我们可以使用方法
scala> val f3:(Int)=>Int = m4
f3: Int => Int = <function1>
scala> //不期望出现函数的地方,方法并不会自动转换成函数
scala> val v3 = m4
<console>:8: error: missing arguments for method m4;
follow this method with `_‘ if you want to treat it as a partially applied function
val v3 = m4
^
利用这种自动转换,我们可以写出很简洁的代码,如下面这样
scala> //10.<被解释成obj.method,即整形的<的方法,所以该表达式是一个方法,会被解释成函数
scala> myList.filter(10.<)
res18: List[Int] = List(56, 72)
因为在scala中操作符被解释称方法
前缀操作符:op obj 被解释称obj.op
中缀操作符:obj1 op obj2被解释称obj1.op(obj2)
后缀操作符:obj op被解释称obj.op
你可以写成10<而不是10.<
scala> myList.filter(10<)
warning: there were 1 feature warning(s); re-run with -feature for details
res19: List[Int] = List(56, 72)
如何强制把一个方法变成函数
可以在方法名后面加一个下划线强制变成函数,部分应用函数
scala> val f4 = m4 _
f4: Int => Int = <function1>
scala> f4(2)
res20: Int = 6
传名参数是一个方法
传名参数实质是一个没有参数列表的方法。正是因此你才可以使用名字调用而不用添加()
scala> //使用两次‘x‘,意味着进行了两次方法调用
scala> def m1(x: => Int)=List(x,x)
m1: (x: => Int)List[Int]
scala> import util.Random
import util.Random
scala> val r = new Random()
r: scala.util.Random = scala.util.Random@d4c330b
scala> //因为方法被调用了两次,所以两个值不相等
scala> m1(r.nextInt)
res21: List[Int] = List(-159079888, -453797380)
如果你在方法体部分缓存了传名参数(函数),那么你就缓存了值(因为x函数被调用了一次)
scala> //把传名参数代表的函数缓存起来
scala> def m1(x: => Int) ={val y=x;List(y,y)}
m1: (x: => Int)List[Int]
scala> m1(r.nextInt)
res22: List[Int] = List(-1040711922, -1040711922)
能否在函数体部分引用传名参数所代表的方法呢,是可以的(缓存的是传名参数所代表的方法)。
scala> def m1(x: => Int)={ val y=x _; List(y(),y()) }
m1: (x: => Int)List[Int]
scala> m1(r.nextInt)
res23: List[Int] = List(1677134799, 180926366)
推荐阅读
(点击标题可跳转阅读)
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
扫码二维码
获取更多精彩
长按图片关注
点个
在看
你最好看