Scala是一門函數式語言,接下來我們會講一下幾個概念:
- 高階函數
- 方法嵌套
- 多參數列表
- 樣例類
- 模式匹配
- 單例對象
- 正則表達式模式
- For表達式
高階函數
高階函數通常來講就是函數的函數,也就是說函數的輸出參數是函數或者函數的返回結果是函數。在Scala中函數是一等公民。
我們看一下Scala集合類(collections)的高階函數map:
<code>val
salaries = Seq(20000
,70000
,40000
)val
doubleSalary = (x:Int
) => x *2
val
newSalaries = salaries.map(doubleSalary) /<code>
map接收一個函數為參數。所以map是一個高階函數,map也可直接接收一個匿名函數,如下所示:
<code>val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(x =>
x
*2
) // List(40000
,140000
,80000
) /<code>
在上面的例子中,我們並沒有顯示使用x:Int的形式,這是因為編譯器可以通過類型推斷推斷出x的類型,對其更簡化的形式是:
<code>val salaries = Seq(20000, 70000, 40000)
val newSalaries = salaries.map(_
*2
) /<code>
既然Scala編譯器已經知道了參數的類型(一個單獨的Int),你可以只給出函數的右半部分,不過需要使用_代替參數名(在上一個例子中是x)
強制轉換方法為函數
如果你傳入一個方法到高階函數中,scala會將該方法強制轉換成函數,如下所示:
<code>case class WeeklyWeatherForecast(temperatures: Seq[Double]
) { private def convertCtoF(temp: Double) = temp *1.8
+32
def forecastInFahrenheit: Seq[Double]
= temperatures.map(convertCtoF) // /<code>
在這個例子中,方法convertCtoF被傳入forecastInFahrenheit。這是可以的,因為編譯器強制將方法convertCtoF轉成了函數x => convertCtoF(x) (注: x是編譯器生成的變量名,保證在其作用域是唯一的)。
方法嵌套
在Scala的方法中可以嵌套方法,如下所示:
<code>def
factorial
(x: Int)
: Int = {def
fact
(x: Int, accumulator: Int)
: Int = {if
(x <=1
) accumulatorelse
fact(x -1
, x * accumulator) } fact(x,1
) } println("Factorial of 2: "
+ factorial(2
)) println("Factorial of 3: "
+ factorial(3
)) /<code>
程序輸出為:
<code>Factorial of 2:
2
Factorial of 3:
6
/<code>
多參數列表
Scala和java不同的是他可以定義多個參數列表,下面是一個例子:
<code>def foldLeft[B
](z: B
)(op: (B, A) => B): B /<code>
可以看到該方法定義了兩個參數列表, z是初始值,op是一個二元運算,下面是它的一個調用:
<code>val
numbers
=
List(1,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
)
val
res
=
numbers.foldLeft(0)((m,
n)
=>
m
+
n)
print(res)
//
55
/<code>
利用scala的類型推斷,我們可以讓代碼更加簡潔:
<code>numbers
.foldLeft
(0
)(_ + _) /<code>
樣例類
case class主要用於不可變的數據。他們和普通類幾乎是一樣的。
<code>caseclass
Book
(isbn: String)val
frankenstein = Book("978-0486282114"
) /<code>
實例化案例類的時候並不需要new關鍵字,因為case class有一個默認的apply方法來負責對象的創建。
在case class中,參數是public並且val的,這意味著case class的參數不可變:
<code>case
class
Message(sender:String
, recipient:String
, body:String
) val message1 = Message(, ,"Ça va ?"
) println(message1.sender) message1.sender = /<code>
這裡message1.sender不能被重新賦值,因為他是val(不可變)的。
比較
case class的比較是按值比較的,而不是按引用:
<code>caseclass
Message
(sender: String, recipient: String, body: String)val
message2 = Message(, ,"Com va?"
)val
message3 = Message(, ,"Com va?"
)val
messagesAreTheSame = message2 == message3 /<code>
雖然上面是不同的對象,但是因為他們的值相同,所以最後的比較是true。
拷貝
可以使用copy來做case class的淺拷貝。
<code>case
class
Message(sender:String
, recipient:String
, body:String
) val message4 = Message(, ,"Me zo o komz gant ma amezeg"
) val message5 = message4.copy(sender = message4.recipient, recipient = ) message5.sender message5.recipient message5.body /<code>
模式匹配
scala中使用match關鍵字和case來做模式匹配,類似java中的switch。
下面是一個簡單的模式匹配的例子:
<code>import scala.util.Random val x: Int = Random.nextInt(10
) x match {case
0
=>"zero"
case
1
=>"one"
case
2
=>"two"
case
_ =>"other"
} /<code>
最後一個case _表示匹配其餘所有情況。
match表達式是有值的,如下所示:
<code>def matchTest(x: Int):String
= x match {case
1
=>"one"
case
2
=>"two"
case
_
=>"other"
} matchTest(3
) matchTest(1
) /<code>
case也可以匹配case class, 如下所示:
<code>abstract
class
Notificationcase
class
Email(sender:String
, title:String
, body:String
)extends
Notificationcase
class
SMS(caller:String
, message:String
)extends
Notificationcase
class
VoiceRecording(contactName:String
, link:String
)extends
Notification def showNotification(notification: Notification):String
= { notification match {case
Email(sender, title, _) => s"You got an email from $sender with title: $title"
case
SMS(number
, message) => s"You got an SMS from $number! Message: $message"
case
VoiceRecording(name, link) => s"you received a Voice Recording from $name! Click the link to hear it: $link"
} } val someSms = SMS("12345"
,"Are you there?"
) val someVoiceRecording = VoiceRecording("Tom"
,"voicerecording.org/id/123"
) println(showNotification(someSms)) println(showNotification(someVoiceRecording)) /<code>
case後面還可以加if語句,我們稱之為模式守衛。
<code>def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String
]):String
= { notification match {case
Email(sender, _, _)if
importantPeopleInfo.contains(sender) =>"You got an email from special someone!"
case
SMS(number
, _)if
importantPeopleInfo.contains(number
) =>"You got an SMS from special someone!"
case
other
=> showNotification(other) } } /<code>
也可以只做類型匹配:
<code>abstract
class
Devicecase
class
Phone(model:String
)extends
Device { def screenOff ="Turning screen off"
}case
class
Computer(model:String
)extends
Device { def screenSaverOn ="Turning screen saver on..."
} def goIdle(device: Device) = device match {case
p:Phone
=> p.screenOffcase
c:Computer
=> c.screenSaverOn } /<code>
密封類
特質(trait)和類(class)可以用sealed標記為密封的,這意味著其所有子類都必須與之定義在相同文件中。
<code>sealedabstract
class
Furniturecase
class
Couch()extends
Furniturecase
class
Chair()extends
Furniture def findPlaceToSit(piece: Furniture):String
= piece match {case
a:Couch
=>"Lie on the couch"
case
b:Chair
=>"Sit on the chair"
} /<code>
單例對象
單例對象是一種特殊的類,可以使用關鍵字object來表示。單例對象是延時創建的,只有當他被第一次使用的時候才會創建。
<code>package
loggingobject
Logger { def info(message: String):Unit
= println(s"INFO:
$message
") } /<code>
單例對象的一個作用就是定義功能性方法,可以在任何地方被使用,如上例中的info方法。可以像如下的方式使用:
<code>import
logging.Logger.infoclass
Project
(name: String, daysToComplete:Int
)class
Test
{val
project1 = new Project("TPS Reports"
,1
)val
project2 = new Project("Website redesign"
,5
) info("Created projects"
) } /<code>
伴生對象
伴生對象是指與某個類名相同的單例對象,類和它的伴生對象可以互相訪問其私有成員。下面是一個伴生對象的例子:
<code>import
scala.math._ caseclass
Circle
(radius:Double
) {import
Circle._ def area:Double
= calculateArea(radius) }object
Circle {private
def calculateArea(radius:Double
):Double
= Pi * pow(radius,2.0
) }val
circle1 = Circle(5.0
) circle1.area /<code>
伴生對象circle1可以訪問類中定義的area.
注意:類和它的伴生對象必須定義在同一個源文件裡。
正則表達式模式
在Scala中,可以使用.r方法將任意字符串變成一個正則表達式。如下所示:
<code>import
scala.util.matching.Regex val numberPattern:Regex
="[0-9]"
.r numberPattern.findFirstMatchIn("awesomepassword"
) match {case
Some
(_
) =>println
("Password OK"
)case
None
=>println
("Password must contain a number"
) } /<code>
你還可以使用括號來同時匹配多組正則表達式。
<code>import
scala.util.matching.Regex val keyValPattern: Regex ="([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)"
.r val input: String ="""background-color: #A03300; |background-image: url(img/header100.png); |background-position: top center; |background-repeat: repeat-x; |background-size: 2160px 108px; |margin: 0; |height: 108px; |width: 100%;"""
.stripMarginfor
(patternMatch "key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") /<code>
For表達式
在Scala中for循環是和yield一起使用的,他的形式是for (enumerators) yield e。 此處 enumerators 指一組以分號分隔的枚舉器。這裡的enumerator 要麼是一個產生新變量的生成器,要麼是一個過濾器。for 表達式在枚舉器產生的每一次綁定中都會計算 e 值,並在循環結束後返回這些值組成的序列。 如下所示:
<code>case
class
User(name:String
, age: Int) val userBase = List(User("Travis"
,28
), User("Kelly"
,33
), User("Jennifer"
,44
), User("Dennis"
,23
)) val twentySomethings =for
(user if (user.age >=20
&& user.age30
))yield
user.name twentySomethings.foreach(name
=> println(name)) /<code>
下面是一個更加複雜的例子:
<code>def
foo
(n: Int, v: Int
) =for
(i 0 until n; j if i + j == v)yield
(i, j) foo(10
,10
)foreach
{case
(i, j) => println(s"($i, $j) "
) } /<code>
你可以在使用 for 表達式時省略 yield 語句。此時會返回 Unit。
歡迎關注我的公眾號:程序那些事,更多精彩等著您!
更多內容請訪問:flydean的博客 flydean.com