寫給Java程序員的Scala入門教程
之前因為Spark的引入,寫了一篇《寫給Python程序員的Scala入門教程》。那篇文章簡單對比了Scala與Python的異同,并介紹了一些Scala的常用編程技巧。今天這篇文章將面向廣大的Java程序員,帶領Javaer進入函數式編程的世界。
Java 8擁有了一些初步的函數式編程能力:閉包等,還有新的并發編程模型及Stream這個帶高階函數和延遲計算的數據集合。在嘗試了Java 8以后,也許會覺得意猶未盡。是的,你會發現Scala能滿足你在初步嘗試函數式編程后那求知的欲望。
安裝Scala
到Scala官方下載地址下載:http://scala-lang.org/download/:
wget -c http://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgz tar zxf scala-2.11.8.tgz cd scala-2.11.8 ./bin/scala Welcome to Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60). Type in expressions to have them evaluated. Type :help for more information. scala>
RELP
剛才我們已經啟動了Scala RELP,它是一個基于命令行的交互式編程環境。對于有著Python、Ruby等動態語言的同學來說,這是一個很常用和工具。但Javaer們第一次見到會覺得比較神奇。我們可以在RELP中做一些代碼嘗試而不用啟動笨拙的IDE,這在我們思考問題時非常的方便。對于Javaer有一個好消息,JDK 9開始將內建支持RELP功能。
對于Scala常用的IDE(集成開發環境),推薦使用IDEA for scala plugins和scala-ide。
Scala的強大,除了它自身對多核編程更好的支持、函數式特性及一些基于Scala的第3方庫和框架(如:Akka、Playframework、Spark、Kafka……),還在于它可以無縫與Java結合。所有為Java開發的庫、框架都可以自然的融入Scala環境。當然,Scala也可以很方便的Java環境集成,比如:Spring。若你需要第3方庫的支持,可以使用Maven、Gradle、Sbt等編譯環境來引入。
Scala是一個面向對象的函數式特性編程語言,它繼承了Java的面向對特性,同時又從Haskell
等其它語言那里吸收了很多函數式特性并做了增強。
變量、基礎數據類型
Scala中變量不需要顯示指定類型,但需要提前聲明。這可以避免很多命名空間污染問題。Scala有一個很強大的類型自動推導功能,它可以根據右值及上下文自動推導出變量的類型。你可以通過如下方式來直接聲明并賦值。
scala> val a = 1 a: Int = 1 scala> val b = true b: Boolean = true scala> val c = 1.0 c: Double = 1.0 scala> val a = 30 + "歲" a: String = 30歲
Immutable
(注:函數式編程有一個很重要的特性:不可變性。Scala中除了變量的不可變性,它還定義了一套不可變集合scala.collection.immutable._
。)
val
代表這是一個final variable,它是一個常量。定義后就不可以改變,相應的,使用var
定義的就是平常所見的變量了,是可以改變的。從終端的打印可以看出,Scala從右值自動推導出了變量的類型。Scala可以如動態語言似的編寫代碼,但又有靜態語言的編譯時檢查。這對于Java中冗長、重復的類型聲明來說是一種很好的進步。
(注:在RELP
中,val
變量是可以重新賦值的,這是`RELP`的特性。在平常的代碼中是不可以的。)
基礎數據類型
Scala中基礎數據類型有:Byte、Short、Int、Long、Float、Double,Boolean,Char、String。和Java不同的是,Scala中沒在區分原生類型和裝箱類型,如:int
和Integer
。它統一抽象成Int
類型,這樣在Scala中所有類型都是對象了。編譯器在編譯時將自動決定使用原生類型還是裝箱類型。
字符串
Scala中的字符串有3種。
- 分別是普通字符串,它的特性和Java字符串一至。
- 連線3個雙引號在Scala中也有特殊含義,它代表被包裹的內容是原始字符串,可以不需要字符轉碼。這一特性在定義正則表達式時很有優勢。
- 還有一種被稱為“字符串插值”的字符串,他可以直接引用上下文中的變量,并把結果插入字符串中。
scala> val c2 = '楊' c2: Char = 楊 scala> val s1 = "重慶譽存企業信用管理有限公司" s1: String = 重慶譽存企業信用管理有限公司 scala> val s2 = s"重慶譽存企業信用管理有限公司${c2}景" s2: String = 重慶譽存企業信用管理有限公司 scala> val s3 = s"""重慶譽存企業信用管理有限公司"工程師"\n${c2}景是江津人""" s3: String = 重慶譽存企業信用管理有限公司"工程師" 楊景是江津人
運算符和命名
Scala中的運算符其實是定義在對象上的方法(函數),你看到的諸如:3 + 2
其實是這樣子的:3.+(2)
。+
符號是定義在Int
對象上的一個方法。支持和Java一至的運算符(方法):
(注:在Scala中,方法前的.
號和方法兩邊的小括號在不引起歧義的情況下是可以省略的。這樣我們就可以定義出很優美的DSL
)
==
、!=
:比較運算!
、|
、&
、^
:邏輯運算>>
、<<
:位運算
注意
在Scala中,修正了(算更符合一般人的常規理解吧)==
和!=
運算符的含義。在Scala中,==
和!=
是執行對象的值比較,相當于Java中的equals
方法(實際上編譯器在編譯時也是這么做的)。而對象的引用比較需要使用eq
和ne
兩個方法來實現。
控制語句(表達式)
Scala中支持if
、while
、for comprehension
(for表達式)、match case
(模式匹配)四大主要控制語句。Scala不支持switch
和? :
兩種控制語句,但它的if
和match case
會有更好的實現。
if
Scala支持if
語句,其基本使用和Java
、Python
中的一樣。但不同的時,它是有返回值的。
(注:Scala是函數式語言,函數式語言還有一大特性就是:表達式。函數式語言中所有語句都是基于“表達式”的,而“表達式”的一個特性就是它會有一個值。所有像Java
中的? :
3目運算符可以使用if
語句來代替)。
scala> if (true) "真" else "假" res0: String = 真 scala> val f = if (false) "真" else "假" f: String = 假 scala> val unit = if (false) "真" unit: Any = () scala> val unit2 = if (true) "真" unit2: Any = 真
可以看到,if
語句也是有返回值的,將表達式的結果賦給變量,編譯器也能正常推導出變量的類型。unit
和unit2
變量的類型是Any
,這是因為else
語句的缺失,Scala編譯器就按最大化類型來推導,而Any
類型是Scala中的根類型。()
在Scala中是Unit
類型的實例,可以看做是Java
中的Void
。
while
Scala中的while
循環語句:
while (條件) {
語句塊
}
for comprehension
Scala中也有for
表達式,但它和Java
中的for
不太一樣,它具有更強大的特性。通常的for
語句如下:
for (變量 <- 集合) {
語句塊
}
Scala中for
表達式除了上面那樣的常規用法,它還可以使用yield
關鍵字將集合映射為另一個集合:
scala> val list = List(1, 2, 3, 4, 5) list: List[Int] = List(1, 2, 3, 4, 5) scala> val list2 = for (item <- list) yield item + 1 list2: List[Int] = List(2, 3, 4, 5, 6)
還可以在表達式中使用if
判斷:
scala> val list3 = for (item <- list if item % 2 == 0) yield item list3: List[Int] = List(2, 4)
還可以做flatMap
操作,解析2維列表并將結果攤平(將2維列表拉平為一維列表):
scala> val llist = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) llist: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) scala> for { | l <- llist | item <- l if item % 2 == 0 | } yield item res3: List[Int] = List(2, 4, 6, 8)
看到了,Scala中for comprehension
的特性是很強大的。Scala的整個集合庫都支持這一特性,包括:Seq
、Map
、Set
、Array
……
Scala沒有C-Like語言里的for (int i = 0; i < 10; i++)
語法,但Range
(范圍這個概念),可以基于它來實現循環迭代功能。在Scala中的使用方式如下:
scala> for (i <- (0 until 10)) { | println(i) | } 0 1 2 3 4 5 6 7 8 9
Scala中還有一個to
方法:
scala> for (i <- (0 to 10)) print(" " + i) 0 1 2 3 4 5 6 7 8 9 10
你還可以控制每次步進的步長,只需要簡單的使用by
方法即可:
scala> for (i <- 0 to 10 by 2) print(" " + i) 0 2 4 6 8 10
match case
模式匹配,是函數式語言很強大的一個特性。它比命令式語言里的switch
更好用,表達性更強。
scala> def level(s: Int) = s match { | case n if n >= 90 => "優秀" | case n if n >= 80 => "良好" | case n if n >= 70 => "良" | case n if n >= 60 => "及格" | case _ => "差" | } level: (s: Int)String scala> level(51) res28: String = 差 scala> level(93) res29: String = 優秀 scala> level(80) res30: String = 良好
可以看到,模式匹配可以實現switch
相似的功能。但與switch
需要使用break
明確告知終止之后的判斷不同,Scala中的match case
是默認break的。只要其中一個case
語句匹配,就終止之后的所以比較。且對應case
語句的表達式值將作為整個match case
表達式的值返回。
Scala中的模式匹配還有類型匹配、數據抽取、謂詞判斷等其它有用的功能。這里只做簡單介紹,之后會單獨一個章節來做較詳細的解讀。
集合
在java.util
包下有豐富的集合庫。Scala除了可以使用Java定義的集合庫外,它還自己定義了一套功能強大、特性豐富的scala.collection
集合庫API。
在Scala中,常用的集合類型有:List
、Set
、Map
、Tuple
、Vector
等。
List
Scala中List
是一個不可變列表集合,它很精妙的使用遞歸結構定義了一個列表集合。
scala> val list = List(1, 2, 3, 4, 5) list: List[Int] = List(1, 2, 3, 4, 5)
除了之前使用List
object來定義一個列表,還可以使用如下方式:
scala> val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil list: List[Int] = List(1, 2, 3, 4, 5)
List
采用前綴操作的方式(所有操作都在列表頂端(開頭))進行,::
操作符的作用是將一個元素和列表連接起來,并把元素放在列表的開頭。這樣List
的操作就可以定義成一個遞歸操作。添加一個元素就是把元素加到列表的開頭,List只需要更改下頭指針,而刪除一個元素就是把List的頭指針指向列表中的第2個元素。這樣,List
的實現就非常的高效,它也不需要對內存做任何的轉移操作。List
有很多常用的方法:
scala> list.indexOf(3) res6: Int = 2 scala> 0 :: list res8: List[Int] = List(0, 1, 2, 3, 4, 5) scala> list.reverse res9: List[Int] = List(5, 4, 3, 2, 1) scala> list.filter(item => item == 3) res11: List[Int] = List(3) scala> list res12: List[Int] = List(1, 2, 3, 4, 5) scala> val list2 = List(4, 5, 6, 7, 8, 9) list2: List[Int] = List(4, 5, 6, 7, 8, 9) scala> list.intersect(list2) res13: List[Int] = List(4, 5) scala> list.union(list2) res14: List[Int] = List(1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9) scala> list.diff(list2) res15: List[Int] = List(1, 2, 3)
Scala中默認都是Immutable collection,在集合上定義的操作都不會更改集合本身,而是生成一個新的集合。這與Java集合是一個根本的區別,Java集合默認都是可變的。
Tuple
Scala中也支持Tuple(元組)這種集合,但最多只支持22個元素(事實上Scala中定義了Tuple0
、Tuple1
……Tuple22
這樣22個TupleX
類,實現方式與C++ Boost
庫中的Tuple
類似)。和大多數語言的Tuple類似(比如:Python),Scala也采用小括號來定義元組。
scala> val tuple1 = (1, 2, 3) tuple1: (Int, Int, Int) = (1,2,3) scala> tuple1._2 res17: Int = 2 scala> val tuple2 = Tuple2("楊", " ) tuple2: (String, String) = (楊,景)
可以使用xxx._[X]
的形式來引用Tuple
中某一個具體元素,其_[X]
下標是從1開始的,一直到22(若有定義這么多)。
Set
Set
是一個不重復且無序的集合,初始化一個Set
需要使用Set
對象:
scala> val set = Set("Scala", "Java", "C++", "Javascript", "C#", "Python", "PHP") set: scala.collection.immutable.Set[String] = Set(Scala, C#, Python, Javascript, PHP, C++, Java) scala> set + "Go" res21: scala.collection.immutable.Set[String] = Set(Scala, C#, Go, Python, Javascript, PHP, C++, Java) scala> set filterNot (item => item == "PHP") res22: scala.collection.immutable.Set[String] = Set(Scala, C#, Python, Javascript, C++, Java)
Map
Scala中的Map
默認是一個HashMap,其特性與Java版的HashMap
基本一至,除了它是Immutable
的:
scala> val map = Map("a" -> "A", "b" -> "B") map: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B) scala> val map2 = Map(("b", "B"), ("c", "C")) map2: scala.collection.immutable.Map[String,String] = Map(b -> B, c -> C)
Scala中定義Map
時,傳入的每個Entry
(K、V對)其實就是一個Tuple2
(有兩個元素的元組),而->
是定義Tuple2
的一種便捷方式。
scala> map + ("z" -> "Z") res23: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B, z -> Z) scala> map.filterNot(entry => entry._1 == "a") res24: scala.collection.immutable.Map[String,String] = Map(b -> B) scala> val map3 = map - "a" map3: scala.collection.immutable.Map[String,String] = Map(b -> B) scala> map res25: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B)
Scala的immutable collection并沒有添加和刪除元素的操作,其定義+
(List
使用::
在頭部添加)操作都是生成一個新的集合,而要刪除一個元素一般使用 -
操作直接將Key從map
中減掉即可。
(注:Scala中也scala.collection.mutable._
集合,它定義了不可變集合的相應可變集合版本。一般情況下,除非一此性能優先的操作(其實Scala集合采用了共享存儲的優化,生成一個新集合并不會生成所有元素的復本,它將會和老的集合共享大元素。因為Scala中變量默認都是不可變的),推薦還是采用不可變集合。因為它更直觀、線程安全,你可以確定你的變量不會在其它地方被不小心的更改。)
Class
Scala里也有class
關鍵字,不過它定義類的方式與Java有些區別。Scala中,類默認是public的,且類屬性和方法默認也是public的。Scala中,每個類都有一個“主構造函數”,主構造函數類似函數參數一樣寫在類名后的小括號中。因為Scala沒有像Java那樣的“構造函數”,所以屬性變量都會在類被創建后初始化。所以當你需要在構造函數里初始化某些屬性或資源時,寫在類中的屬性變量就相當于構造初始化了。
在Scala中定義類非常簡單:
class Person(name: String, val age: Int) { override def toString(): String = s"姓名:$name, 年齡: $age" }
默認,Scala主構造函數定義的屬性是private的,可以顯示指定:val
或var
來使其可見性為:public。
Scala中覆寫一個方法必需添加:override
關鍵字,這對于Java來說可以是一個修正。當標記了override
關鍵字的方法在編譯時,若編譯器未能在父類中找到可覆寫的方法時會報錯。而在Java中,你只能通過@Override
注解來實現類似功能,它的問題是它只是一個可選項,且編譯器只提供警告。這樣你還是很容易寫出錯誤的“覆寫”方法,你以后覆寫了父類函數,但其實很有可能你是實現了一個新的方法,從而引入難以察覺的BUG。
實例化一個類的方式和Java一樣,也是使用new
關鍵字。
scala> val me = new Person("楊景", 30) me: Person = 姓名:楊景, 年齡: 30 scala> println(me) 姓名:楊景, 年齡: 30 scala> me.name <console>:20: error: value name is not a member of Person me.name ^ scala> me.age res11: Int = 30
case class(樣本類)
case class
是Scala中學用的一個特性,像Kotlin
這樣的語言也學習并引入了類似特性(在Kotlin
中叫做:data class
)。case class
具有如下特性:
- 不需要使用
new
關鍵詞創建,直接使用類名即可 - 默認變量都是public final的,不可變的。當然也可以顯示指定
var
、private
等特性,但一般不推薦這樣用 - 自動實現了:
equals
、hashcode
、toString
等函數 - 自動實現了:
Serializable
接口,默認是可序列化的 - 可應用到match case(模式匹配)中
- 自帶一個
copy
方法,可以方便的根據某個case class
實例來生成一個新的實例 - ……
這里給出一個case class
的使用樣例:
scala> trait Person defined trait Person scala> case class Man(name: String, age: Int) extends Person defined class Man scala> case class Woman(name: String, age: Int) extends Person defined class Woman scala> val man = Man("楊景", 30) man: Man = Man(楊景,30) scala> val woman = Woman("女人", 23) woman: Woman = Woman(女人,23) scala> val manNextYear = man.copy(age = 31) manNextYear: Man = Man(楊景,31)
object
Scala有一種不同于Java的特殊類型,Singleton Objects。
object Blah { def sum(l: List[Int]): Int = l.sum }
在Scala中,沒有Java里的static靜態變量和靜態作用域的概念,取而代之的是:object。它除了可以實現Java里static的功能,它同時還是一個線程安全的單例類。
伴身對象
大多數的object
都不是獨立的,通常它都會與一個同名的class
定義在一起。這樣的object
稱為伴身對象。
class IntPair(val x: Int, val y: Int) object IntPair { import math.Ordering implicit def ipord: Ordering[IntPair] = Ordering.by(ip => (ip.x, ip.y)) }
注意
伴身對象必需和它關聯的類定義定義在同一個.scala文件。
伴身對象和它相關的類之間可以相互訪問受保護的成員。在Java程序中,很多時候會把static成員設置成private的,在Scala中需要這樣實現此特性:
class X { import X._ def blah = foo } object X { private def foo = 42 }
函數
在Scala中,函數是一等公民。函數可以像類型一樣被賦值給一個變量,也可以做為一個函數的參數被傳入,甚至還可以做為函數的返回值返回。
從Java 8開始,Java也具備了部分函數式編程特性。其Lamdba函數允許將一個函數做值賦給變量、做為方法參數、做為函數返回值。
在Scala中,使用def
關鍵ygnk來定義一個函數方法:
scala> def calc(n1: Int, n2: Int): (Int, Int) = { | (n1 + n2, n1 * n2) | } calc: (n1: Int, n2: Int)(Int, Int) scala> val (add, sub) = calc(5, 1) add: Int = 6 sub: Int = 5
這里定義了一個函數:calc
,它有兩個參數:n1
和n2
,其類型為:Int
。cala
函數的返回值類型是一個有兩個元素的元組,在Scala中可以簡寫為:(Int, Int)
。在Scala中,代碼段的最后一句將做為函數返回值,所以這里不需要顯示的寫return
關鍵字。
而val (add, sub) = calc(5, 1)
一句,是Scala中的抽取功能。它直接把calc
函數返回的一個Tuple2
值賦給了add
他sub
兩個變量。
函數可以賦給變量:
scala> val calcVar = calc _ calcVar: (Int, Int) => (Int, Int) = <function2> scala> calcVar(2, 3) res4: (Int, Int) = (5,6) scala> val sum: (Int, Int) => Int = (x, y) => x + y sum: (Int, Int) => Int = <function2> scala> sum(5, 7) res5: Int = 12
在Scala中,有兩種定義函數的方式:
- 將一個現成的函數/方法賦值給一個變量,如:
val calcVar = calc _
。下劃線在此處的含意是將函數賦給了變量,函數本身的參數將在變量被調用時再傳入。 - 直接定義函數并同時賦給變量,如:
val sum: (Int, Int) => Int = (x, y) => x + y
,在冒號之后,等號之前部分:(Int, Int) => Int
是函數簽名,代表sum
這個函數值接收兩個Int類型參數并返回一個Int類型參數。等號之后部分是函數體,在函數函數時,x
、y
參數類型及返回值類型在此可以省略。
一個函數示例:自動資源管理
在我們的日常代碼中,資源回收是一個很常見的操作。在Java 7之前,我們必需寫很多的try { ... } finally { xxx.close() }
這樣的樣版代碼來手動回收資源。Java 7開始,提供了try with close這樣的自動資源回收功能。Scala并不能使用Java 7新加的try with close資源自動回收功能,但Scala中有很方便的方式實現類似功能:
def using[T <: AutoCloseable, R](res: T)(func: T => R): R = { try { func(res) } finally { if (res != null) res.close() } } val allLine = using(Files.newBufferedReader(Paths.get("/etc/hosts"))) { reader => @tailrec def readAll(buffer: StringBuilder, line: String): String = { if (line == null) buffer.toString else { buffer.append(line).append('\n') readAll(buffer, reader.readLine()) } } readAll(new StringBuilder(), reader.readLine()) } println(allLine)
using
是我們定義的一個自動化資源管幫助函數,它接愛兩個參數化類型參數,一個是實現了AutoCloseable
接口的資源類,一個是形如:T => R
的函數值。func
是由用戶定義的對res
進行操作的函數代碼體,它將被傳給using
函數并由using
代執行。而res
這個資源將在using
執行完成返回前調用finally
代碼塊執行.close
方法來清理打開的資源。
這個:T <: AutoCloseable
范型參數限制了T類型必需為AutoCloseable
類型或其子類。R
范型指定using
函數的返回值類型將在實際調用時被自動參數化推導出來。我們在Scala Console中參看allLine
變量的類型可以看到 allLine
將被正確的賦予String類型,因為我們傳給using
函數參數func
的函數值返回類型就為String:
scala> :type allLine
String
在readAll
函數的定義處,有兩個特別的地方:
- 這個函數定義在了其它函數代碼體內部
- 它有一個
@tailrec
注解
在Scala中,因為函數是第一類的,它可以被賦值給一個變量。所以Scala中的def
定義函數可以等價val func = (x: Int, y: Int) => x + y
這個的函數字面量定義函數形式。所以,既然通過變量定義的函數可以放在其它函數代碼體內,通過def
定義的函數也一樣可以放在其它代碼體內,這和Javascript很像。
@tailrec
注解的含義是這個函數是尾遞歸函數,編譯器在編譯時將對其優化成相應的while循環。若一個函數不是尾遞歸的,加上此注解在編譯時將報錯。
模式匹配(match case)
模式匹配是函數式編程里面很強大的一個特性。
之前已經見識過了模式匹配的簡單使用方式,可以用它替代:if else、switch這樣的分支判斷。除了這些簡單的功能,模式匹配還有一系列強大、易用的特性。
match 中的值、變量和類型
scala> for { | x <- Seq(1, false, 2.7, "one", 'four, new java.util.Date(), new RuntimeException("運行時異常")) | } { | val str = x match { | case d: Double => s"double: $d" | case false => "boolean false" | case d: java.util.Date => s"java.util.Date: $d" | case 1 => "int 1" | case s: String => s"string: $s" | case symbol: Symbol => s"symbol: $symbol" | case unexpected => s"unexpected value: $unexpected" | } | println(str) | } int 1 boolean false double: 2.7 string: one symbol: 'four java.util.Date: Sun Jul 24 16:51:20 CST 2016 unexpected value: java.lang.RuntimeException: 運行時異常
上面小試牛刀校驗變量類型的同時完成類型轉換功能。在Java中,你肯定寫過或見過如下的代碼:
public void receive(message: Object) { if (message isInstanceOf String) { String strMsg = (String) message; .... } else if (message isInstanceOf java.util.Date) { java.util.Date dateMsg = (java.util.Date) message; .... } .... }
對于這樣的代碼,真是辣眼睛啊~~~。
序列的匹配
scala> val nonEmptySeq = Seq(1, 2, 3, 4, 5) scala> val emptySeq = Seq.empty[Int] scala> val emptyList = Nil scala> val nonEmptyList = List(1, 2, 3, 4, 5) scala> val nonEmptyVector = Vector(1, 2, 3, 4, 5) scala> val emptyVector = Vector.empty[Int] scala> val nonEmptyMap = Map("one" -> 1, "two" -> 2, "three" -> 3) scala> val emptyMap = Map.empty[String, Int] scala> def seqToString[T](seq: Seq[T]): String = seq match { | case head +: tail => s"$head +: " + seqToString(tail) | case Nil => "Nil" | } scala> for (seq <- Seq( | nonEmptySeq, emptySeq, nonEmptyList, emptyList, | nonEmptyVector, emptyVector, nonEmptyMap.toSeq, emptyMap.toSeq)) { | println(seqToString(seq)) | } 1 +: 2 +: 3 +: 4 +: 5 +: Nil Nil 1 +: 2 +: 3 +: 4 +: 5 +: Nil Nil 1 +: 2 +: 3 +: 4 +: 5 +: Nil Nil (one,1) +: (two,2) +: (three,3) +: Nil Nil
模式匹配能很方便的抽取序列的元素,seqToString
使用了模式匹配以遞歸的方式來將序列轉換成字符串。case head +: tail
將序列抽取成“頭部”和“非頭部剩下”兩部分,head
將保存序列第一個元素,tail
保存序列剩下部分。而case Nil
將匹配一個空序列。
case class的匹配
scala> trait Person scala> case class Man(name: String, age: Int) extends Person scala> case class Woman(name: String, age: Int) extends Person scala> case class Boy(name: String, age: Int) extends Person scala> val father = Man("父親", 33) scala> val mather = Woman("母親", 30) scala> val son = Man("兒子", 7) scala> val daughter = Woman("女兒", 3) scala> for (person <- Seq[Person](father, mather, son, daughter)) { | person match { | case Man("父親", age) => println(s"父親今年${age}歲") | case man: Man if man.age < 10 => println(s"man is $man") | case Woman(name, 30) => println(s"${name}今年有30歲") | case Woman(name, age) => println(s"${name}今年有${age}歲") | } | } 父親今年33歲 母親今年有30歲 man is Man(兒子,7) 女兒今年有3歲
在模式匹配中對case class
進行解構操作,可以直接提取出感興趣的字段并賦給變量。同時,模式匹配中還可以使用guard語句,給匹配判斷添加一個if
表達式做條件判斷。
并發
Scala是對多核和并發編程的支付做得非常好,它的Future類型提供了執行異步操作的高級封裝。
Future對象完成構建工作以后,控制權便會立刻返還給調用者,這時結果還不可以立刻可用。Future實例是一個句柄,它指向最終可用的結果值。不論操作成功與否,在future操作執行完成前,代碼都可以繼續執行而不被阻塞。Scala提供了多種方法用于處理future。
scala> :paste // Entering paste mode (ctrl-D to finish) import scala.concurrent.duration.Duration import scala.concurrent.{Await, Future} import scala.concurrent.ExecutionContext.Implicits.global val futures = (0 until 10).map { i => Future { val s = i.toString print(s) s } } val future = Future.reduce(futures)((x, y) => x + y) val result = Await.result(future, Duration.Inf) // Exiting paste mode, now interpreting. 0132564789 scala> val result = Await.result(future, Duration.Inf) result: String = 0123456789
上面代碼創建了10個Future
對象,Future.apply
方法有兩個參數列表。第一個參數列表包含一個需要并發執行的命名方法體(by-name body);而第二個參數列表包含了隱式的ExecutionContext
對象,可以簡單的把它看作一個線程池對象,它決定了這個任務將在哪個異步(線程)執行器中執行。futures
對象的類型為IndexedSeq[Future[String]]
。本示例中使用Future.reduce
把一個futures
的IndexedSeq[Future[String]]
類型壓縮成單獨的Future[String]
類型對象。Await.result
用來阻塞代碼并獲取結果,輸入的Duration.Inf
用于設置超時時間,這里是無限制。
這里可以看到,在Future
代碼內部的println
語句打印輸出是無序的,但最終獲取的result
結果卻是有序的。這是因為雖然每個Future
都是在線程中無序執行,但Future.reduce
方法將按傳入的序列順序合并結果。
除了使用Await.result
阻塞代碼獲取結果,我們還可以使用事件回調的方式異步獲取結果。Future
對象提供了幾個方法通過回調將執行的結果返還給調用者,常用的有:
- onComplete: PartialFunction[Try[T], Unit]:當任務執行完成后調用,無論成功還是失敗
- onSuccess: PartialFunction[T, Unit]:當任務成功執行完成后調用
- onFailure: PartialFunction[Throwable, Unit]:當任務執行失敗(異常)時調用
import scala.concurrent.Future import scala.util.{Failure, Success} import scala.concurrent.ExecutionContext.Implicits.global val futures = (1 to 2) map { case 1 => Future.successful("1是奇數") case 2 => Future.failed(new RuntimeException("2不是奇數")) } futures.foreach(_.onComplete { case Success(i) => println(i) case Failure(t) => println(t) }) Thread.sleep(2000)
futures.onComplete
方法是一個偏函數,它的參數是:Try[String]
。Try
有兩個子類,成功是返回Success[String]
,失敗時返回Failure[Throwable]
,可以通過模式匹配的方式獲取這個結果。
總結
本篇文章簡單的介紹了Scala的語言特性,本文并不只限于Java程序員,任何有編程經驗的程序員都可以看。現在你應該對Scala有了一個基礎的認識,并可以寫一些簡單的代碼了。我在博客中分享了一些《Scala實戰(系列)》文章,介紹更函數式的寫法及與實際工程中結合的例子。也歡迎對Scala感興趣的同學與我聯系經,一起交流、學習。
原文鏈接:http://www.yangbajing.me/2016/07/24/寫給java程序員的scala入門教程/