在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
參數列表對於方法是可選的,但是對於函數是強制的
方法的可以沒有參數列表,參數列表也可以為空。但是函數必須有參數列表(也可以為空),見下面例子
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>
那么方法為什么可以沒有參數列表呢,往下看。
方法名意味着方法調用,函數名只是代表函數自身
因為方法不能作為最終的表達式存在,所以如果你寫了一個方法的名字並且該方法不帶參數(沒有參數列表或者無參)
該表達式的意思是:調用該方法得到最終的表達式。因為函數可以作為最終表達式出現,如果你寫下函數的名字,函數
調用並不會發生,該方法自身將作為最終的表達式進行返回,如果要強制調用一個函數,你必須在函數名后面寫()
scala> //該方法沒有參數列表 scala> m2 res11: Int = 100 scala> //該方法有一個空的參數列表 scala> m3 res12: Int = 100 scala> //得到函數自身,不會發生函數調用 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.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(-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)