一、前言
大數據領域的Spark、Kafka、Summingbird等都是由Scala語言編寫而成,相比Java而言,Scala更精煉。由於筆者從事大數據相關的工作,所以有必要好好學習Scala語言,之前也學習過,但是沒有記錄,所以就會忘記,感覺Scala確實比Java方便精煉很多,下面以Scala Cookbook英文版作為參考資料,從頭到尾梳理Scala相關知識點,也加深印象。PS:這是在研究Zookeeper源碼的間隙中交叉學習,不至於總是看源碼太枯燥。
二、String
在Scala的REPL環境中測試String的類型,可以發現其就是Java中的String。
所以可以使用Java中String的所有方法,如獲取字符串的長度、連接多個字符串。在Scala中,由於String可以被隱式轉化成StringOps類型,可將字符串看成一個字符序列,並且可以使用foreach方法遍歷字符串的每個字符。
也可將字符串當成字符序列,使用for循環遍歷每個字符
同理,也可將字符串當前字節序列,使用for循環遍歷
由於字符串可以當成字符順序集合(字符序列),而在集合上可進行多種操作,如filter
可以看到對字符串進行了過濾,去掉了l字符。filter方法是StringOps的方法,由於String會被隱式的轉化為StringOps,因此可以調用此方法。
對於該隱式轉化,是在Perdef對象中定義。
@inline implicit def augmentString(x: String): StringOps = new StringOps(x)
向封閉類中添加方法
在Java中,String被定義成final的,即無法繼承String類或者添加任何方法,但在Scala中,我們可以通過隱式轉化添加方法,下面例子展示了Scala的String擁有String的特性和集合的特性。
其中,drop方法和take方法都是Scala的序列(集合)的方法,而capitalize方法則是StringOps的方法,這種調用都是通過隱式轉化完成的。
2.1 測試String的相等性
1. 問題描述
你需要比較兩個字符串以判斷它們是否相等,或者它們包含相同的字符序列。
2. 解決方案
定義了如下字符串s1、s2、s3
使用"=="進行相等性判斷
可以看到s1、s2、s3均相等,而對於一個好的"=="方法而言,即便是有參數為空,也不會拋出異常。
若在比較過程中不區分大小寫,則可將字符串轉化成大寫或小寫進行比較。
然而,在對空字符串調用toUpperCase方法會拋出異常。
在Java中,想要比較不區分大小寫比較兩個字符串是否相等,則使用equalsIgnoreCase方法
3. 討論
在Scala中,我們使用==方法來判定對象的相等性,這與Java不同,Java使用equals方法判定兩個對象的相等性,== 是比較兩個對象是否是同一個對象(內存地址相同)。在Scala中,== 方法在AnyRef(所有引用類型的父類)中定義,其首先會檢查空值,然后再調用第一個對象的equals方法進行比較,因此,當比較兩個字符串是否相等時,我們不需要檢查空值。
2.2 創建多行字符串
1. 問題描述
在Scala源碼中想要創建多行字符串。
2. 解決方案
在Scala中,你可以使用三個雙引號來創建多行字符串。
val s1 = """This is a multiline String """ println(s1)
3. 討論
使用上述方法可以創建多行字符串,但是當打印時,其結果如下
This is a multiline String
第二行和第三行是以空格開頭,若需要使第二行、第三行的字符串都不以空格開頭,則可進行如下處理。
val s1 = """ |This |is a multiline |String """.stripMargin println(s1)
結果如下
This is a multiline String
即通過|來處理,或者使用其他符號,如#,同時使用stripMargin方法。
val s1 = """This is #a multiline #String """.stripMargin('#') println(s1)
結果如下
This is a multiline String
上述例子中,在第一行is和第二行multiline后面均隱藏了\n字符進行換行,當需要將多行字符串合並為一行時,可以在使用stripMargin方法之后使用replaceAll方法來將所有的\n替換為" "。
另外,在三引號字符串中可以包含特殊字符,而並不需要轉義符進行轉義。
2.3 分割字符串
1. 問題描述
你需要使用分割字符來分割字符串,如從逗號分隔(CSV)或管道分隔的文件中獲取字符串。
2. 解決方案
可以使用String的split方法進行分割
split方法會返回String數組。
3. 討論
split函數的參數可以是正則表達式,所以對於CSV文件,你可以使用逗號進行分割字符串。
可以看到,通過","進行分割時,結果中還包含了一些空格,如" milk", " butter", " Coco Puffs",此時,需要使用trim函數來去掉空格。
我們也可以使用正則表達式來分割字符串
split方法是重載的,一部分從Java的String而來,一部分從Scala的StringLike而來,例如,你可以使用字符而非字符串作為參數來調用split,此時,你使用的是StringLike的方法
此時,使用字符和字符串作為參數兩者的結果是相同的。
2.4 將變量替換成字符串
1. 問題描述
如同Perl、PHP、Ruby一樣,你需要將變量替換成字符串。
2. 解決方案
為在Scala中使用字符串插值,字符串前面需要使用字母s,同時需要將變量包含在字符串中,而每個變量名前面是一個$字符。
當在字符串前面使用字母s時,表示正在創建一個已處理的字符串字面量,即可以在字符串中直接使用變量。
字符串字面量中使用表達式
除了在字符串中使用變量外,同樣也可以在字符串中使用表達式,此時表達式需要放在中括號內。
也可以使用中括號打印對象的屬性。
s是一個方法
放在字符串字面量前面的s實際上是一個方法,而使用s方法可以讓你享受如下便利
· Scala提供了其他現成的插值函數
· 你可自定義字符串插值函數
f字符串插值(printf格式)
在討論中提到,weight被打印成65,但是如果需要在weight后面增加多位小數點應該怎么做,可以使用f字符串插值方法,其可以格式化字符串中的說明符。
為使用f字符串插值,首先需要在字符串前面添加f,然后在變量后面使用printf格式化說明符。
粗插值
除了使用s和f插值方法外,Scala還包括了粗插值方法,其會保留字符串中的特殊字符。
可以看到使用raw修飾字符串時,其會保留字符串中的特殊字符。
下表列出了最常用的說明符
2.5 一次處理字符串的一個字符
1. 問題描述
你需要遍歷字符串的每個字符,並且對每個字符做相應的操作。
2. 解決方案
可以使用map方法、foreach方法、for循環等方法來遍歷字符串。
或者使用下划線的方式
對於字符串的字符序列,你可以使用鏈式調用來得到想要的結果,在下面的示例中,filter方法用於原始的字符串來生成新的字符串(去掉所有的字符l),然后再調用map方法將新生成的字符串轉化為大寫。
使用for循環和yield也可以達到map方法的效果
map方法、for和yield方法可以將舊的集合轉化為新的集合,而foreach方法則是對集合的每個元素進行操作,不會產生新的結果。
3. 討論
由於Scala將字符串當成是字符序列,而Scala也是面向對象和函數編程語言,在Java中,你可以使用如下方法來遍歷字符串中的每個字符
String s = "hello" for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); System.out.println(c); }
理解map方法的工作機制
在map方法中,你可以傳入一大段代碼塊
上述功能是將字符串的字符由大寫變換成小寫,由於是調用的String的map方法,因此每次只會處理字符串的一個字符,map會將String當成一個字符順序集合,map方法有一個隱式循環,在此循環中,每次只會傳入一個字符。除了在map方法中直接傳入代碼塊外,也可以先定義好函數,然后再傳入map,這樣可以保證代碼的簡潔性。
並且也可以在for循環和yield中使用該方法
除了使用方法方式外,還可以使用函數方式來完成上述的操作
2.6 在字符串中查找模式
1. 問題描述
你需要確定字符串是否包含正則表達式模式。
2. 解決方案
通過String的.r方法來創建一個Regex對象,之后當查找第一個匹配時,使用findFirstIn方法,而查找所有匹配時,使用findAllIn方法。
對於findAllIn方法而言,可以將結果轉化成Array、List、Seq等
3. 討論
使用字符串的.r方法是創建Regex對象最簡單的方式,另外一種方式是導入Regex類,創建一個Regex實例,之后使用實例的方法
處理findFirstIn返回的結果
findFirstIn查找第一個匹配並且返回一個Option[String]
Option/Some/None類型將會在之后的章節進行討論,可以簡單的認為Option是一個容器,它要么持有0或者一個值,對於findFirstIn,成功時會返回Some("123"),不成功時會返回None
返回類型為Option[String]的方法要么返回Some(String),要么返回None
針對Option類型,想要獲取其值,可以使用如下方法
· getOrElse
· 使用foreach
· 使用匹配表達式
使用getOrElse方法,你可以嘗試獲取值,或者失敗時定義缺省值
使用foreach方法如下
使用匹配表達式方法如下
2.7 字符串的替換模式
1. 問題描述
你需要要在字符串中搜索正則表達式模式,並替換它們。
2. 解決方案
由於String是不可變的,所以你不能直接在字符串上進行查找並替換的操作,但是你可以創建包含替換內容的新字符串,可以使用replaceAll方法,要記得將結果賦值
也可創建一個正則表達式,然后調用replaceAllIn方法,同樣要記得將結果賦值給新的字符串
也可以調用replaceFirstIn來替換第一個匹配值,同樣要記得將結果賦值給新的字符串
2.8 提取模式匹配的字符串部分
1. 問題描述
你需要提取字符串的一個或多個匹配正則達表達式的部分。
2. 解決方案
首先定義提取的正則表達式模式,然后將它們放置在括號里面形成正則表達式組
上述示例從指定字符串中提取了數字部分和字母部分,並且分別賦值給count和fruit。
3. 討論
上述示例的語法可能有點古怪,似乎是將模式兩次定義成val字段,但是這種語法很便捷並且可讀性很高,可以試想你在編寫一個搜索引擎,你想讓人們用各種各樣的短語搜索電影,你可以讓他們輸入任何這些短語來獲得電影列表
你可以定義一系列的正則表達式進行匹配,例如
// match "movies 80301" val MoviesZipRE = "movies (\\d{5})".r // match "movies near boulder, co" val MoviesNearCityStateRE = "movies near ([a-z]+), ([a-z]{2})".r
之后可以根據用戶的輸入進行匹配,然后獲取搜索結果,偽代碼如下
textUserTyped match { case MoviesZipRE(zip) => getSearchResults(zip) case MoviesNearCityStateRE(city, state) => getSearchResults(city, state) case _ => println("did not match a regex") }
上述的正則表達式可以匹配如下字符串
在匹配時,需要考慮所有情況,如case _ 表示不能匹配,如下字符串將無法匹配
2.9 訪問字符串的字符
1. 問題描述
你想要獲取字符串中特定位置的字符。
2. 解決方案
可以使用Java的charAt方法
另外,一種更好的方法是數組表示法
3. 討論
當map方法和foreach不適用時,可以將String看做Array類型,然后使用數組表示法來訪問字符,Scala中的數組表示法不同於Java的數組表示法,因為在Scala中,數組表示法是一個方法調用。
在調用數組表示法時實際上調用的是apply方法,,但是由於Scala語法糖的存在,可以直接使用數組表示法獲取指定字符。
2.10 向String類添加自定義方法
1. 問題描述
你想要向字符串類添加自定義方法,如"HAL".increment,而非使用工具類StringUtilities的increment方法。
2. 解決方案
可以定義一個隱式類,然后在類中定義你想要添加的方法
在實際的編碼中會稍微復雜一點,因為隱式類需要必須要被定義在可以被定義方法的作用域中,這意味着隱式類必須定義在class、object、package object中。
package com.leesf.utils object StringUtils { implicit class StringImprovements(val s: String) { def increment = s.map(c => (c + 1).toChar) } }
在使用時,需要導入com.leesf.utils.StringUtils
package foo.bar import com.leesf.utils.StringUtils._ object Main extends App { println("HAL".increment) }
還可將隱式類放在包對象中
package com.leesf package object utils { implicit class StringImprovements(val s: String) { def increment = s.map(c => (c + 1).toChar) } }
在使用時,需要導入com.leesf.utils
package foo.bar import com.leesf.utils._ object MainDriver extends App { println("HAL".increment) }
針對Scala2.10前的版本,與上述做法有稍許不同,首先需要在普通的類中定義increment方法
class StringImprovements(val s: String) { def increment = s.map(c => (c + 1).toChar) }
然后定義隱式方法進行轉化
implicit def stringToString(s: String) = new StringImprovements(s)
3. 討論
在Scala中,你可以通過隱式轉化向封閉的類添加新的方法,並且在使用時導入,而不需要繼承該類來添加方法(有些final類根本不能繼承)。
三、總結
本篇博文講解了Scala中的String知識點,其也是在Scala編程中使用頻率非常高的知識點,對其進行了梳理,加深了印象,也謝謝各位園友的觀看~