寫給Java程序員的Scala入門教程

(原文連接:http://www.yangbajing.me/2016/07/24/寫給java程序員的scala入門教程/,轉載請註明)java

以前由於Spark的引入,寫了一篇《寫給Python程序員的Scala入門教程》。那篇文章簡單對比了Scala與Python的異同,並介紹了一些Scala的經常使用編程技巧。今天這篇文章將面向廣大的Java程序員,帶領Javaer進入函數式編程的世界。python

Java 8擁有了一些初步的函數式編程能力:閉包等,還有新的併發編程模型及Stream這個帶高階函數和延遲計算的數據集合。在嘗試了Java 8之後,也許會以爲意猶未盡。是的,你會發現Scala能知足你在初步嘗試函數式編程後那求知的慾望。程序員

安裝Scala

到Scala官方下載地址下載:http://scala-lang.org/download/es6

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功能。spring


對於Scala經常使用的IDE(集成開發環境),推薦使用IDEA for scala pluginsscala-ideexpress

Scala的強大,除了它自身對多核編程更好的支持、函數式特性及一些基於Scala的第3方庫和框架(如:Akka、Playframework、Spark、Kafka……),還在於它能夠無縫與Java結合。全部爲Java開發的庫、框架均可以天然的融入Scala環境。固然,Scala也能夠很方便的Java環境集成,好比:Spring。若你須要第3方庫的支持,可使用MavenGradleSbt等編譯環境來引入。apache

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中沒在區分原生類型和裝箱類型,如:intInteger。它統一抽象成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方法(實際上編譯器在編譯時也是這麼作的)。而對象的引用比較須要使用eqne兩個方法來實現。

控制語句(表達式)

Scala中支持ifwhilefor comprehension(for表達式)、match case(模式匹配)四大主要控制語句。Scala不支持switch? :兩種控制語句,但它的ifmatch case會有更好的實現。

if

Scala支持if語句,其基本使用和JavaPython中的同樣。但不一樣的時,它是有返回值的。

(注: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語句也是有返回值的,將表達式的結果賦給變量,編譯器也能正常推導出變量的類型。unitunit2變量的類型是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的整個集合庫都支持這一特性,包括:SeqMapSetArray……

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中,經常使用的集合類型有:ListSetMapTupleVector等。

List

Scala中List是一個不可變列表集合,它很精妙的使用遞歸結構定義了一個列表集合。

scala> val list = List(1, 2, 3, 4, 5)
list: List[Int] = List(1, 2, 3, 4, 5)

除了以前使用Listobject來定義一個列表,還可使用以下方式:

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中定義了Tuple0Tuple1……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時,傳入的每一個EntryKV對)其實就是一個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使用::在頭部添加)操做都是生成一個新的集合,而要刪除一個元素通常使用 - 操做直接將Keymap中減掉便可。

(注: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的,能夠顯示指定:valvar來使其可見性爲: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具備以下特性:

  1. 不須要使用new關鍵詞建立,直接使用類名便可
  2. 默認變量都是public final的,不可變的。固然也能夠顯示指定varprivate等特性,但通常不推薦這樣用
  3. 自動實現了:equalshashcodetoString等函數
  4. 自動實現了:Serializable接口,默認是可序列化的
  5. 可應用到match case(模式匹配)中
  6. 自帶一個copy方法,能夠方便的根據某個case class實例來生成一個新的實例
  7. ……

這裏給出一個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,它有兩個參數:n1n2,其類型爲:Intcala函數的返回值類型是一個有兩個元素的元組,在Scala中能夠簡寫爲:(Int, Int)。在Scala中,代碼段的最後一句將作爲函數返回值,因此這裏不須要顯示的寫return關鍵字。

val (add, sub) = calc(5, 1)一句,是Scala中的抽取功能。它直接把calc函數返回的一個Tuple2值賦給了addsub兩個變量。

函數能夠賦給變量:

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中,有兩種定義函數的方式:

  1. 將一個現成的函數/方法賦值給一個變量,如:val calcVar = calc _。下劃線在此處的含意是將函數賦給了變量,函數自己的參數將在變量被調用時再傳入。
  2. 直接定義函數並同時賦給變量,如:val sum: (Int, Int) => Int = (x, y) => x + y,在冒號以後,等號以前部分:(Int, Int) => Int是函數簽名,表明sum這個函數值接收兩個Int類型參數並返回一個Int類型參數。等號以後部分是函數體,在函數函數時,xy參數類型及返回值類型在此能夠省略。

一個函數示例:自動資源管理

在咱們的平常代碼中,資源回收是一個很常見的操做。在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函數的定義處,有兩個特別的地方:

  1. 這個函數定義在了其它函數代碼體內部
  2. 它有一個@tailrec註解

在Scala中,由於函數是第一類的,它能夠被賦值給一個變量。因此Scala中的def定義函數能夠等價val func = (x: Int, y: Int) => x + y這個的函數字面量定義函數形式。因此,既然經過變量定義的函數能夠放在其它函數代碼體內,經過def定義的函數也同樣能夠放在其它代碼體內,這和Javascript很像。

@tailrec註解的含義是這個函數是尾遞歸函數,編譯器在編譯時將對其優化成相應的while循環。若一個函數不是尾遞歸的,加上此註解在編譯時將報錯。

模式匹配(match case)

模式匹配是函數式編程裏面很強大的一個特性。

以前已經見識過了模式匹配的簡單使用方式,能夠用它替代:if elseswitch這樣的分支判斷。除了這些簡單的功能,模式匹配還有一系列強大、易用的特性。

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把一個futuresIndexedSeq[Future[String]]類型壓縮成單獨的Future[String]類型對象。Await.result用來阻塞代碼並獲取結果,輸入的Duration.Inf用於設置超時時間,這裏是無限制。

這裏能夠看到,在Future代碼內部的println語句打印輸出是無序的,但最終獲取的result結果倒是有序的。這是由於雖然每一個Future都是在線程中無序執行,但Future.reduce方法將按傳入的序列順序合併結果。

除了使用Await.result阻塞代碼獲取結果,咱們還可使用事件回調的方式異步獲取結果。Future對象提供了幾個方法經過回調將執行的結果返還給調用者,經常使用的有:

  1. onComplete: PartialFunction[Try[T], Unit]:當任務執行完成後調用,不管成功仍是失敗
  2. onSuccess: PartialFunction[T, Unit]:當任務成功執行完成後調用
  3. 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感興趣的同窗與我聯繫經,一塊兒交流、學習。

相關文章
相關標籤/搜索