Scala學習總結 一、Scala簡介 Scala特點: Scala 是一門多範式 (multi-paradigm) 的編程語言 ,設計初衷是要集成面向對象編程和函數式編程的各種 特性。 Scala 是一門以 java 虛擬機 (JVM) 為運行環境的編程語言 ,Scala 源代碼(.scala)會 ...
Scala學習總結
一、Scala簡介
Scala特點:
- Scala 是一門多範式 (multi-paradigm) 的編程語言 ,設計初衷是要集成面向對象編程和函數式編程的各種 特性。
- Scala 是一門以 java 虛擬機 (JVM) 為運行環境的編程語言 ,Scala 源代碼(.scala)會被編譯成 Java 位元組碼(.class) ,然後運行於 JVM 之上 ,並可以調用現有的 Java 類庫 ,實現兩種語言的無縫對接。 強類型語言
- 簡潔高效 (各種語法糖)
- 源於java ,與java對比學習 ,更易掌握
二、變數
1. 基本語法
先聲明再使用:
val/var 變數名 [:變數類型] = 變數值 |
註意: 變數類型可以省略 ,變數類型確定後就無法改變 (強類型語言) var修飾的變數可以改變 ,val修飾的變數 不可變。 val是線程安全的 ,效率更高 ,val修飾的變數在編譯後 ,等同於加上了final 。
變數聲明時需要初始值。
2. 數據類型
Scala 與 Java 有著相同的數據類型 ,在 Scala 中數據類型都是對象 ,也就是說 scala 沒有 java 中的原生類 型。 Scala 數據類型分為兩大類 AnyVal(值類型) 和 AnyRef(引用類型) , 註意:不管是 AnyVal 還是 AnyRef 都是對象。
2.1. 數據類型一覽圖
說明:
1. Any 是所有類的根類型,即所有類的父類(基類)。
2. 在 scala 中類分為兩個大的類型分支(AnyVal [值類型 ,即可以理解成就是 java 的基本數據類型], AnyRef 類型)。
3. 在 AnyVal 雖然叫值類型 ,但是仍然是類(對象)。
4. 在 scala 中有兩個特別的類型(Null ), 還有一個是 Nothing。
5. Null 類型只有一個實例 null, 他是 bottom class ,是 AnyRef 的子類。
6. Nothing 類型是所有類的子類 , 它的價值是在於因為它是所有類的子類 ,就可以將 Nothing 類型的對象返回 給任意的變數或者方法 ,比如案例:
def f1():Nothing= { //表示 f1 方法就是沒有正常的返回值 ,專門用於返回異常 throw new Exception("異常發生") } |
7. 在 scala 中仍然遵守 低精度的數據自動的轉成 高精度的數據類型。
8. 在 scala 中 , Unit 類型比較特殊 ,這個類型也只有一個實例 () 。
2.2. 整數類型
2.2.1 整數類型的分類
數據類型 |
描述 |
Byte |
8位有符號補碼整數。數值區間為 -128 到 127 |
Short |
16位有符號補碼整數。數值區間為 -32768 到 32767 |
Int |
32位有符號補碼整數。數值區間為 -2147483648 到 2147483647 |
Long |
64位有符號補碼整數。數值區間為 -9223372036854775808 到 9223372036854775807 |
2.2.2 整型的使用細節
Scala 各整數類型有固定的表數範圍和欄位長度 ,不受具體 OS 的影響 ,以保證 Scala 程式的可移植性。
Scala 的整型 常量/字面量 預設為 Int 型 ,聲明 Long 型 常量/字面量 須後加‘l’’或‘L’ 。
Scala 程式中變數常聲明為 Int 型 ,除非不足以表示大數 ,才使用 Long。
2.3. 浮點類型
2.3.1 浮點類型的分類
數據類型 |
描述 |
Float |
32 位, IEEE 754 標準的單精度浮點數 |
Double |
64 位, IEEE 754 標準的雙精度浮點數 |
2.3.2 浮點型使用細節
與整數類型類似 ,Scala 浮點類型也有固定的表數範圍和欄位長度 ,不受具體 OS 的影響。 Scala 的浮點型常量預設為 Double 型 ,聲明 Float 型常量 ,須後加‘f’或‘F’。
通常情況下 ,應該使用 Double 型 ,因為它比 Float 型更精確(小數點後大致 7 位)。
2.4. 字元類型(Char)
字元常量是用單引號(‘ ’)括起來的單個字元。例如:var c1 = 'a‘ var c2 = '中‘ var c3 = '9' 2) Scala 也允許使用轉 義字元‘\’來將其後的字元轉變為特殊字元型常量。例如:var c3 = ‘\n’ // '\n' 表示換行符可以直接給 Char 賦一個整數 ,然後輸出時 ,會按照對應的 unicode 字元輸出 ['\u0061' 97] Char 類型是可以進行運算的 ,相當於一個整數 ,因為它都對應有 Unicode 碼。
2.5. 布爾類型(Boolean)
布爾類型也叫 Boolean 類型 , Booolean 類型數據只允許取值 true 和 false
boolean 類型占 1 個位元組。 boolean 類型適於邏輯運算 ,一般用於程式流程式控制制
2.6. Unit 類型、 Null 類型和 Nothing 類型
數據類型 |
描述 |
Unit |
表示無值 ,和其他語言中void等同。用作不返回任何結果的方法的結果類型。 Unit只有一個實 例值 ,寫成() |
Null |
null 或空引用 |
Nothing |
Nothing類型在Scala的類層級的最底端;它是任何其他類型的子類型 |
2.6.2 使用細節
Unit 類型用來標識過程 ,也就是沒有明確返回值的函數。 由此可見 , Unit 類似於 Java 里的void。 Unit 只有 一個實例 ,() ,這個實例也沒有實質的意義
Null 類只有一個實例對象 , null ,類似於 Java 中的 null 引用。 null 可以賦值給任意引用類型(AnyRef) ,但是 不能賦值給值類型(AnyVal: 比如 Int, Float, Char, Boolean, Long, Double, Byte, Short)
Nothing ,可以作為沒有正常返回值的方法的返回類型 ,非常直觀的告訴你這個方法不會正常返回 ,而且由於 Nothing 是其他任意類型的子類 ,他還能跟要求返回值的方法相容。
二、運算符
1. 運算符介紹
運算符是一種特殊的符號 ,用以表示數據的運算、賦值和比較等。
算術運算符
賦值運算符
關係運算符
邏輯運算符
位運算符
2. 運算符一覽表
2.1. 算術運算符
假定變數 A 為 10 , B 為 20:
運算符 |
描述 |
實例 |
+ |
加號 |
A + B 運算結果為 30 |
- |
減號 |
A - B 運算結果為 -10 |
* |
乘號 |
A * B 運算結果為 200 |
/ |
除號 |
B / A 運算結果為 2 |
% |
取餘 |
B % A 運算結果為 0 |
2.2. 賦值運算符
Scala 中沒有++、--操作符 ,需要通過+=、-=來實現同樣的效果
運算符 |
描述 |
實例 |
= |
簡單的賦值運算 ,指定右邊操作數賦值給左邊的操作數。 |
C = A + B 將 A + B 的運算結果賦 值給 C |
+= |
相加後再賦值 ,將左右兩邊的操作數相加後再賦值給左邊的 操作數。 |
C += A 相當於 C = C + A |
-= |
相減後再賦值 ,將左右兩邊的操作數相減後再賦值給左邊的 操作數。 |
C -= A 相當於 C = C - A |
*= |
相乘後再賦值 ,將左右兩邊的操作數相乘後再賦值給左邊的 操作數。 |
C *= A 相當於 C = C * A |
/= |
相除後再賦值 ,將左右兩邊的操作數相除後再賦值給左邊的 操作數。 |
C /= A 相當於 C = C / A |
%= |
求餘後再賦值 ,將左右兩邊的操作數求餘後再賦值給左邊的 操作數。 |
C %= A is equivalent to C = C % A |
<<= |
按位左移後再賦值 |
C <<= 2 相當於 C = C << 2 |
>>= |
按位右移後再賦值 |
C >>= 2 相當於 C = C >> 2 |
&= |
按位與運算後賦值 |
C &= 2 相當於 C = C & 2 |
^= |
按位異或運算符後再賦值 |
C ^= 2 相當於 C = C ^ 2 |
|= |
按位或運算後再賦值 |
C |= 2 相當於 C = C | 2 |
2.3. 關係運算符
關係運算符的結果都是 Boolean 型 ,也就是要麼是true ,要麼是 false。關係運算符組成的表達式 ,我們稱為關係 表達式。 如果兩個浮點數進行比較 ,應當保證數據類型一致.
運算符 |
描述 |
實例 |
== |
等於 |
(A == B) 運算結果為 false |
!= |
不等於 |
(A != B) 運算結果為 true |
> |
大於 |
(A > B) 運算結果為 false |
< |
小於 |
(A < B) 運算結果為 true |
>= |
大於等於 |
(A >= B) 運算結果為 false |
<= |
小於等於 |
(A <= B) 運算結果為 true |
2.4. 邏輯運算符
假定變數 A 為 1 , B 為 0:
運算符 |
描述 |
實例 |
&& |
邏輯與 |
(A && B) 運算結果為 false |
| | |
邏輯或 |
(A | | B) 運算結果為 true |
! |
邏輯非 |
!(A && B) 運算結果為 true |
2.5. 位運算符
位運算符用來對二進位位進行操作
運算符 |
描述 |
& |
按位與運算符 |
| |
按位或運算符 |
^ |
按位異或運算符 |
~ |
按位取反運算符 |
<< |
左移動運算符 |
>> |
右移動運算符 |
>>> |
無符號右移 |
三、程式流程式控制制
1. if - else
Scala 中任意表達式都是有返回值的 ,也就意味著 if else 表達式其實是有返回結果的 ,具體返回結果的值取 決於滿 足條件的代碼體的最後一行內容。Scala 中是沒有三元運算符 ,但是可以利用這個特性使用if--else進行三元運算。
例如:
val num = StdIn.redInt() val res = if (num > 3) "比3大" else "比三小" println(res) |
1.1. 單分支
if (條件表達式) { 執行代碼塊 } |
1.2. 雙分支
if (條件表達式) { 執行代碼塊1 } else { 執行代碼塊2 } |
1.3. 多分支
if (條件表達式) { 執行代碼塊1 } else if (條件表達式) { 執行代碼塊2 } else if (條件表達式) { 執行代碼塊3 } ... |
1.4. 嵌套分支
if (條件表達式) { if (條件表達式) { 執行代碼塊1 } else { 執行代碼塊2 } } |
2. for 迴圈
2.1. 範圍數據迴圈方式
2.1.1 to方式
for(i < ‐ 1 to 3) { // 這裡的 1 to 3 也可以是一個集合 前後閉合 (包括 1 和 3) print(i + " ") } |
2.1.2 until方式
for(i < ‐ 1 until 3) { //i的取值是1 和 2 ,until表示 前閉後開 的範圍 print(i + " ") } |
2.2. 迴圈守衛
迴圈守衛 ,即迴圈保護式。保護式為 true 則進入迴圈體內部 ,為 false 則跳過 ,類似於 continue。
for(i < ‐ 1 to 3 if i != 2) { //輸出1 3 print(i + " ") } |
2.3. 引入變數
for(i < ‐ 1 to 3; j = 4 ‐ i) {//沒有關鍵字 ,所以要加 ; 隔斷邏輯 print(j + " ") } |
2.4. 嵌套迴圈
for(i < ‐ 1 to 3; j < ‐ 1 to 3) { println(" i =" + i + " j = " + j) } // 等價於 // 在業務複雜時使用 for (i < ‐ 1 to 3) { for (j < ‐1 to 3) { println(" i =" + i + " j = " + j) } } |
2.5. 迴圈返回值(yield)
將遍歷過程中處理的結果返回到一個新的Vector集合中 ,使用yield關鍵字 ,yield可以寫代碼塊。
val res = for(i < ‐ 1 to 10) yield i * 2 println(res) |
2.6. 控制步長
for迴圈的步長控制有兩種方法 ,通常使用迴圈守衛的方式。
例:遍歷 1-10, 步長為 3
2.6.1 Range
Range是一個集合 ,括弧裡面三個數表示: 1: start , 10: end 遍歷到 (end -1) ,3: 表示 step
for (i < ‐ Range(1,10,3)) { //遍歷1 ‐ (10 ‐1),步長3 until println("i=" + i) } |
2.6.2 使用守衛
for (i < ‐ 1 to 10 if i % 3 == 1 ) { println("i=" + i) } |
3. while 迴圈
特點:
- while 迴圈是先判斷再執行語句。
- 與 If 語句不同 ,While 語句本身沒有值 ,即整個 While 語句的結果是 Unit 類型的()
- 因為while 中沒有返回值,所以當要用該語句來計算並返回結果時,就不可避免的使用變數 ,而變數需要聲明在 while 迴圈的外部 ,那麼就等同於迴圈的內部對外部的變數造成了影響 ,所以不推薦使用 ,而是推薦使用for 迴圈。
語法:
while (迴圈條件) { 迴圈體(語句) 迴圈變數迭代 } |
4. do..while 迴圈
迴圈變數初始化; do{ 迴圈體(語句) 迴圈變數迭代 } while(迴圈條件) |
5. while 迴圈的中斷
Scala 內置控制結構特地去掉了 break 和 continue ,是為了更好的適應函數化編程 ,推薦使用函數式的風格解決 break 和 contine 的功能 ,而不是一個關鍵詞。
5.1. 使用breakable控制迴圈的中斷
//使用前需要導包 import util.control.Breaks._ //將需要通過breakable控制的代碼放到breakable的大括弧中 //相當於break,跳出整個迴圈 breakable { for (i < ‐ 1 to 10) { if (i == 5) { break() } println("i=" + i) } } //相當於continue,跳出本次迴圈 ,繼續執行下一次迴圈 for (i < ‐ 1 to 10) { breakable { if (i == 5) { break() } println("i=" + i) } } |
5.2. 使用if-else或迴圈守衛實現continue效果
//當i=4,5時跳過 for(i < ‐ 1 to 10){ if (i != 4 && i != 5) { println("i=" + i) } } for (i < ‐ 1 to 10 if (i != 4 && i != 5)) { println("i=" + i) } |
四、函數式編程
1. 函數式編程介紹
函數式編程是一種"編程範式" (programming paradigm) 。它屬於結構化編程的一種 ,主要思想是把運算過程盡 量寫成一系列嵌套的函數調用。函數式編程中 ,將函數也當做數據類型 ,因此可以接受函數當作輸入 (參數) 和輸 出 (返回值) 。(增強了編程的粒度)
在 Scala 中 ,方法和函數幾乎可以等同(比如他們的定義、使用、運行機制都一樣的) ,只是函數的使用方式 更加的 靈活多樣。當一段功能代碼出現多次時 ,編程時 ,就可以將這段功能代碼抽取出來 ,做成函數 ,供調用。
2. 函數/方法的定義
def 函數名 ([參數名: 參數類型], ...)[[: 返回值類型] =] { 語句 ... //完成某個功能 return 返回值 } |
- 函數聲明關鍵字為 def (definition)
- [參數名: 參數類型], ...:表示函數的輸入(就是參數列表), 可以沒有。 如果有 ,多個參數使用逗號間隔 函數中的語句:表示為了實現某一功能代碼塊
- 函數可以有返回值,也可以沒有 返回值的形式:
- [: 返回值類型] = 表示有返回值 ,並且指定了返回值的類型
- 沒寫返回值類型只有等號, 表示返回值類型 ,使用類型推導
- 空的 ,表示沒有返回值 ,即使有 return 也不生效
- 如果沒有 return ,預設以執行到最後一行的結果作為返回值
3. 函數的調用機制
4. 函數的遞歸調用
一個函數/方法在函數/方法體內又調用了本身 ,我們稱為遞歸調用。
例:
def test(n:Int){ if (n>2){ test(n ‐1) } else { println(s"n = $n") } } // 輸出n=2 |
5. 註意事項
- 函數的形參列表可以是多個, 如果函數沒有形參 ,調用時 可以不帶() 。
- 形參列表和返回值列表的數據類型可以是值類型和引用類型。
- Scala 中的函數可以根據函數體最後一行代碼自行推斷函數返回值類型。那麼在這種情況下 ,return 關鍵字 可以省略。
- 因為 Scala 可以自行推斷 ,所以在省略 return 關鍵字的場合 ,返回值類型也可以省略。
- 如果函數明確使用 return 關鍵字 ,那麼函數返回就不能使用自行推斷了,這時要明確寫成 : 返回類型 = ,當然 如果你什麼都不寫 ,即使有 return , 返回值為()。
- 如果函數明確聲明無返回值 (聲明 Unit) ,那麼函數體中即使使用 return 關鍵字也不會有返回值。
- 如果明確函數無返回值或不確定返回值類型 ,那麼返回值類型可以省略(或聲明為 Any)。
- Scala 語法中任何的語法結構都可以嵌套其他語法結構(靈活) ,即 :函數/方法中可以再聲明/定義函數/方法, 類中可以再聲明類。
- Scala 函數的形參 ,在聲明參數時 ,直接賦初始值(預設值) ,這時調用函數時 ,如果沒有指定實參 ,則會使用 預設值。如果指定了實參 ,則實參會覆蓋預設值。
- 如果存在多個參數 ,每一個參數都可以設定預設值 ,那麼這個時候 ,傳遞的參數到底是覆蓋預設值 ,還是賦 值給沒有預設值的參數 ,就不確定了(預設按照聲明順序[從左到右])。在這種情況下 ,可以採用帶名參數 。 scala 函數的形參預設是 val 的 ,因此不能在函數中進行修改。
- 遞歸函數未執行之前是無法推斷出來結果類型 ,在使用時必須有明確的返回值類型。
- Scala 函數支持可變參數。
6. 過程 (procedure)
將函數的返回類型為 Unit 的函數稱之為過程(procedure) ,如果明確函數沒有返回值 ,那麼等號可以省略。
例:
def f1(name: String): Unit = { println(name + " hello ") } |
如果函數聲明時沒有返回值類型 ,但是有 = 號 ,可以進行類型推斷最後一行代碼。這時這個函數實際是有返回值的 ,該函數並不是過程。
7. 惰性函數
惰性計算 (儘可能延遲表達式求值) 是許多函數式編程語言的特性。惰性集合在需要時提供其元素 ,無需預先計算 它們 ,這帶來了一些好處。首先 ,您可以將耗時的計算推遲到絕對需要的時候。其次 ,您可以創造無限個集合 ,只 要它們繼續收到請求 ,就會繼續提供元素。函數的惰性使用讓您能夠得到更高效的代碼。
當函數返回值被聲明為 lazy 時 ,函數的執行將被推遲 ,直到我們首次對此取值 ,該函數才會執行。這種函數我 們 稱之為惰性函數 ,在 Java的某些框架代碼中稱之為懶載入(延遲載入)。
def main(args: Array[String]): Unit = { lazy val res = sum(1,2) println(" ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ") println("res=" + res) //當需要使用到 res 時 ,就會真正的開始計算 } def sum(n1:Int,n2:Int): Int = { println("sum 被調用 ..") n1 + n2 } |
註意:
- lazy 不能修飾 var 類型的變數
- 不但是 在調用函數時 ,加了 lazy ,會導致函數的執行被推遲 ,我們在聲明一個變數時 ,如果給聲明瞭 lazy , 那麼變數值得分配也會推遲。 比如 lazy val i = 10
8. 異常
Scala 提供 try 和 catch 塊來處理異常。try 塊用於包含可能出錯的代碼。catch 塊用於處理 try 塊中發生的異常。 可以根據需要在程式中有任意數量的 try...catch 塊。語法處理上和 Java 類似 ,但是又不盡相同。
例:
object ScalaException { def main(args: Array[String]): Unit = { //scala 中去掉所謂的 checked (編譯) 異常 //設計者認為 ,如果程式員編程時 ,認為某段代碼可疑 ,就直接 try 並處理 //說明 //1. 如果代碼可疑 ,使用 try 進行處理 //2. 在 catch 中 ,可以有多個 case ,對可能的異常進行匹配 //3. case ex: Exception => println("異常信息=" + ex.getMessage) // (1) case 是一個關鍵字 // (2) ex: Exception 異常的種類 // (3) => 表明後的代碼是對異常進行處理 ,如果處理的代碼有多條語句可以{}擴起 //4. 在 scala 中把範圍小的異常放在後面 ,語法不會報錯 ,但是不推薦 //5. 如果捕獲異常 ,代碼即使出現異常 ,程式也不會崩潰。 try { var res = 10 / 0 } catch { case ex: ArithmeticException => { println("算術異常=" + ex.getMessage) println("111") println("222") } case ex: Exception => println("異常信息=" + ex.getMessage) } finally { println("finaly 的代碼 ...") } println("程式繼續 ....") } } |
註意:
- 我們將可疑代碼封裝在 try 塊中。 在 try 塊之後使用了一個 catch 處理程式來捕獲異常。如果發生任何異常, catch 處理程式將處理它 ,程式將不會異常終止。
- Scala 的異常的工作機制和 Java 一樣 ,但是 Scala 沒有“checked(編譯期)”異常 ,即 Scala 沒有編譯異常這個 概念 ,異常都是在運行的時候捕獲處理。
- 用 throw 關鍵字 ,拋出一個異常對象。所有異常都是 Throwable 的子類型。throw 表達式是有類型的 ,就是 Nothing ,因為 Nothing 是所有類型的子類型 ,所以 throw 表達式可以用在需要類型的地方
例如:
def main(args: Array[String]): Unit = { val res = test() println(res.toString) } def test(): Nothing = { throw new Exception("不對") } |
- 在 Scala 里 ,借用了模式匹配的思想來做異常的匹配 ,因此 ,在 catch 的代碼里 ,是一系列 case 子句來匹配異常。當匹配上後 => 有多條語句可以換行寫 ,類 似 java 的 switch case x: 代碼塊..
- 異常捕捉的機制與其他語言中一樣 ,如果有異常發生 ,catch 子句是按次序捕捉的。 因此 ,在 catch 子句中,越具體的異常越要靠前 ,越普遍的異常越靠後 ,如果把越普遍的異常寫在前 ,把具體的異常寫在後 ,在 scala 中也不會報錯(不報錯 ,但是不推薦) ,但這樣是非常不好的編程風格。
- finally 子句用於執行不管是正常處理還是有異常發生時都需要執行的步驟 ,一般用於對象的清理工作 ,這點和 Java 一樣。
- Scala 提供了 throws 關鍵字來聲明異常。可以使用方法定義聲明異常。 它向調用者函數提供了此方法可能引發此異常的信息。 它有助於調用函數處理並將該代碼包含在 try-catch 塊中 ,以避免程式異常終止。在 scala中 ,可以使用 throws 註釋來聲明異常 例如:
def main(args: Array[String]): Unit = { f11() } @throws(classOf[NumberFormatException]) //等同於 NumberFormatException.class def f11() = { "abc".toInt } |
9. 匿名函數
沒有名字的函數就是匿名函數 ,可以通過函數表達式 ,來設置匿名函數。
val triple = (x: Double) => 3 * x pritnln(triple) // 類型 println(triple(3)) |
說明 :(x: Double) => 3 * x 就是匿名函數 (x: Double) 是形參列表 , => 是規定語法表示後面是函數體 , 3 * x 就是函數體 ,如果有多行 ,可以 {} 換 行寫.triple 是指向匿名函數的變數。
案例:
object NoNameFunction { def main(args: Array[String]): Unit = { //編寫一個匿名函數 ,可以返回 2 個整數的和 ,並輸出該匿名函數的類型 //如果我們定義一個函數 ,則變數名字要寫 val f1 = (x1:Int,x2:Int) => { x1 + x2 } println(f1(10, 30)) // 40 println(f1) // <function2> //調用 f2 完成一個運算 println(f2(f1,30,40)) // 70 // f = f1 } //方法 ,可以接受一個函數 ,該函數返回兩個數的差 //這時 ,我們只是寫一個函數的格式(簽名) def f2(f:(Int,Int) => Int, n1:Int,n2:Int): Int = { f(n1,n2) } } |
10. 高階函數
能夠接受函數作為參數的函數 ,叫做高階函數 (higher-order function)。可使應用程式更加健壯。 高階函數可 以 返回一個匿名函數。
10.1. 高階函數的基本使用
def test(f: Double => Double, n1: Double) = { f(n1) //調用 f 函數 } // def sum(d: Double): Double = { d + d } val res = test(sum, 6.0) println("res=" + res) def minusxy(x: Int) = { (y: Int) => x – y // 函數表達式 , 返回的是一個匿名函數 } //說明 //minusxy 高階函數 返回的是 (y: Int) => x – y 匿名函數 //minusxy(3) => 返回的就是一個具體的匿名函數 (y: Int) => 3 – y
val result3 = minusxy(3)(5) println(result3)
//minusxy(3)(5) => 3 – 5 = ‐2 |
11. 參數(類型)推斷
- 參數類型是可以推斷時 ,可以省略參數類型
- 當傳入的函數 ,只有單個參數時 ,可以省去括弧
- 如果變數只在=>右邊只出現一次 ,可以用_來代替
12. 閉包
如果一個函數 ,訪問到了它的外部 (局部) 變數的值 ,那麼這個函數和他所處的環境 ,稱為閉包。
def minusxy(x: Int) = (y: Int) => x – y //說明 //1. minusxy 返回了 (y: Int) => x – y 匿名函數 //2. 使用到 x 值 ,x 是它引用到的一個環境變數 //3. 匿名函數和 x 組合成一個整體 ,構成了一個閉包 //4. f 就是一個閉包 val f = minusxy(20) println("f(1)=" + f(1)) // 19 println("f(2)=" + f(2)) // 18 |
12.1 定義
在電腦科學中 ,閉包 (英語:Closure) ,又稱詞法閉包 ( Lexical Closure) 或函數閉包 (function closures) ,是在支持頭等函數的編程語言中實現詞法綁定的一種技術。閉包在實現上是一個結構體 ,它存 儲了一個函數 (通常是其入口地址) 和一個關聯的環境 (相當於一個符號查找表) 。環境里是若幹對符號和 值的對應關係 ,它既要包括約束變數 (該函數內部綁定的符號) ,也要包括自由變數 (在函數外部定義但在 函數內被引用) ,有些函數也可能沒有自由變數。閉包跟函數最大的不同在於 ,當捕捉閉包的時候 ,它的自 由變數會在捕捉時被確定 ,這樣即便脫離了捕捉時的上下文 ,它也能照常運行。捕捉時對於值的處理可以是 值拷貝 ,也可以是名稱引用 ,這通常由語言設計者決定 ,也可能由用戶自行指定 (如C++) 。
因為外層調用結束返回內層函數後 ,經過堆棧調整(比如在C中主調或者被調清理) ,外層函數的參數已經被釋放了 ,所以內層是獲取不到外層的函數參數的。為了能夠將環境 (函數中用到的並非該函數參數的變數和他 們的值) 保存下來 (需要考慮釋放問題 ,可以通過GC可以通過對象生命周期控制 ,GC是一個常見選擇) ,這 時會將執行的環境打一個包保存到堆裡面。
13. 函數柯里化 (Currying)
將一個參數列表的多個參數 ,變成多個參數列表的過程。也就是將普通多參數函數變成高階函數的過程。
13.1 定義
在電腦科學中 ,柯里化 (英語:Currying) ,又譯為卡瑞化或加里化 ,是把接受多個參數的函數變換成接 受一個單一參數 (最初函數的第一個參數) 的函數 ,並且返回接受餘下的參數而且返回結果的新函數的技 術。在直覺上 ,柯里化聲稱“如果你固定某些參數 ,你將得到接受餘下參數的一個函數”。柯里化是一種處理 函數中附有多個參數的方法 ,併在只允許單一參數的框架中使用這些函數。
13.2 scala中的柯里化函數
// Currying def add(a: Int)(b: Int): Int = a + b println(add(4)(3)) val addFour = add(4) _ // val addFour: Int => int = add(4) println(addFour(3)) |
14. 控制抽象
值調用:按值傳遞參數 ,計算值後再傳遞。多數語言中一般函數調用都是這個方式 ,C++還存在引用傳遞。
名調用:按名稱傳遞參數 ,直接用實參替換函數中使用形參的地方。能想到的只有C語言中的帶參巨集函數 ,其 實並不是函數調用 ,預處理時直接替換。 例子:
// pass by value def f0(a: Int): Unit = { println("a: " + a) println("a: " + a) } f0(10) // pass by name, argument can be a code block that return to Int def f1(a: => Int): Unit = { println("a: " + a) println("a: " + a) } def f2(): Int = { println("call f2()") 10 } f1(10) f1(f2()) // pass by name, just replace a with f2(), then will call f2() twice f1({ println("code block") // print twice 30 }) |
應用:使用傳名參數實現一個函數相當於while的功能。
// built ‐in while var n = 10 while (n >= 1) { print(s"$n ") n-= 1 } println() // application: self ‐defined while, implement a function just like while keyword def myWhile(condition: => Boolean): (=> Unit) => Unit = { def doLoop(op: => Unit): Unit = { if (condition) { op myWhile(condition)(op) } } doLoop _ } n= 10 myWhile (n >= 1) { print(s"$n ") n ‐= 1 } println() // simplfy def myWhile2(condition: => Boolean): (=> Unit) => Unit = { op => { if (condition) { op myWhile2(condition)(op) } } } n= 10 myWhile (n >= 1) { print(s"$n ") n ‐= 1 } println() // use currying def myWhile3(condition: => Boolean)(op: => Unit): Unit = { if (condition) { op myWhile3(condition)(op) } } n= 10 myWhile3 (n >= 1) { print(s"$n ") n ‐= 1 } println() |