在scala中方法(method)与函数(function) ,method是面向对象的概念而function是函数式的概念.
在scala中“一切都是对象”我们可以将scala中的函数称为函数对象;在scala中def 是个method,当我们需要的时候,可以用方法来表达函数,然后将方法转换成函数:
scala> val a=2
a: Int = 2
scala> a //传递
res1: Int = 2
scala> val aa=(x:Int)=>x*2
aa: Int => Int = <function1>
scala> aa
res2: Int => Int = <function1>
scala> def aa=(x:Int)=>x*2
aa: Int => Int
scala> aa //引用
res3: Int => Int = <function1>
scala> aa() //调用
<console>:9: error: not enough arguments for method apply: (v1: Int)Int in trait Function1.
Unspecified value parameter v1.
aa()
^
scala> aa
res6: Int => Int = <function1>
scala中方法与函数有太多的交集;scala是基于面向对象的设计,为了表达函数的概念,所以以面向对象的形式来表达函数的概念。
1.function与method明显的区别点:
1)方法可以作为一个表达式的一部分出现(调用函数并传参),但是方法(带参方法)不能作为最终的表达式,但是函数可以作为最终的表达式出现:
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+2m1: ()Int scala> m1 res10: Int = 3
2) 参数列表对于方法是可选的,但是对于函数是强制的
方法的可以没有参数列表,参数列表也可以为空。但是函数必须有参数列表(也可以为空),见下面例子
scala> //方法可以没有参数列表scala> def m2 = 100; m2: Int scala> //方法可以有一个空的参数列表 scala> def m3() = 100m3: ()Int scala> //函数必须有参数列表,否则报错 scala> var f1 = => 100 <console>:1: error: illegal start of simple expression var f1 = => 100 ^scala> //函数也可以有一个空的参数列表scala> var f2 = () => 100f2: () => Int = <function0>
那么方法为什么可以没有参数列表呢,往下看。
3)方法名意味着方法调用,函数名只是代表函数自身
因为方法不能作为最终的表达式存在,所以如果你写了一个方法的名字并且该方法不带参数(没有参数列表或者无参)
该表达式的意思是:调用该方法得到最终的表达式。因为函数可以作为最终表达式出现,如果你写下函数的名字,函数
调用并不会发生,该方法自身将作为最终的表达式进行返回,如果要强制调用一个函数,你必须在函数名后面写()
scala> //该方法没有参数列表scala> m2 res11: Int = 100scala> //该方法有一个空的参数列表scala> m3 res12: Int = 100scala> //得到函数自身,不会发生函数调用scala> f2 res13: () => Int = <function0>scala> //调用函数scala> f2() res14: Int = 100
为什么在函数出现的地方我们可以提供一个方法
在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.Randomimport util.Random scala> val r = new Random() r: scala.util.Random = scala.util.Random@d4c330b scala> //因为方法被调用了两次,所以两个值不相等scala> m1(r.nextInt) res21: List[Int] = List(-1273601135, 2004676878)
如果你在方法体部分缓存了传名参数(函数),那么你就缓存了值(因为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(-1982925840, -933815401)
2.方法的一些特殊形式
def f:Int=>Double = {
// 可以看成 def f: (Int=>Double) = {...}
case 1 => 0.1
case 2 => 0.2
case _ => 0.0
}
f(1) // 0.1
f(3) // 0.0
3.过程:如果函数体包含在花括号当中但没有前面的=号,那么返回类型就是Unit。这样的函数被称做过程(procedure)。过程不返回值,我们调用它仅仅是为了它的副作用。
例:
由于过程不返回任何值,所以我们可以略去=号。
def box(s: String) { // 仔细看:没有=号
var border = "-" * s.length + "--\n"
println(border + "|" + s + "|\n" + border)
}
4.匿名函数:函数字面量
函数的字面量是有类型的,(x:Int) => println(x) 的类型是 Int => Unit 。
这个类型同样由两部分组成, =>左边是参数的类型, =>右边是函数体的返回值类型。
函数的字面量类型:从哪里(类型) => 到哪里(类型) 如: Int => Unit
形式:((命名参数列表)=>函数实现)(参数列表)
对比: save() 方法名(参数)
特殊地:
- 无参数: (()=>函数实现)()
- 有一个参数且在最后: (函数实现)(参数)
- 无返回值: ((命名参数列表)=>Unit)(参数列表)
匿名函数过多也可能造成代码的可读性下降,这点在开发的过程中要注意。
5.参数:
1) "_" 匿名函数中的匿名参数
注意:多个下划线指代多个参数,而不是单个参数的重复使用。第一个下划线代表第一个参数,第二个下划线代表第二个,第三个……,如此类推。
((i:Int, j:Int) => i+j)(3, 4) // 7
((_:Int) + (_:Int))(3,4) // 7 注意:括号(_:Int)括号是必须的
"Hello".exists(_.isUpper) // true
如果_在最后,还可以省略
1 to 5 foreach println
1 to 5 map (10*)
1 to 5 map (*10) // 错误!_不在最后不能省
def sum(x:Int,y:Int,z:Int) = x+y+z
val sum1 = sum _
val sum2 = sum(1000,_:Int,100)
sum1(1,2,3) // 6
sum2(5) // 1105
2)变长参数 *
变长参数只能放在最后一个,否则就歧义了,
def sum(ns:Int*, s:String) = ... // 错误
例子1:
def sum(ns: Int*) = {
var s = 0
for (n<-ns) s += n
s
}
sum(1,2,3,4) // 10
更函数化的写法:
def sum(ns:Int*) = ns.reduceLeft(_+_)
sum(1,2,3,4) // 10
例子2:
def m(args:Any*) = args foreach println
scala> m(3,3.14,"hello",List(1,2,3))
3
3.14
hello
List(1, 2, 3)