爲Play初學者準備的Scala基礎知識

歡迎訪問PlayScala社區(http://www.playscala.cn/)

1 前言

本文的主要目的是爲了讓Play Framework的初學者快速瞭解Scala語言,算是一篇Play Framework的入門前傳吧。
使用PlayFramework能夠極大的提升開發效率,可是須要注意,PlayJava入門很簡單,我以前帶過一個實習小姑娘,有一點編程經驗,但歷來沒有接觸過PlayJava,然而一週入門,一個月獨立完成項目。可是PlayScala沒那麼簡單,雖而後者的開發效率更高,可是因爲Scala程序員匱乏,PlayScala只適合團隊較小(10人如下)而且較穩定的狀況下使用。其實有不少人懷疑,Scala到底能提升多少開發效率,這裏有一行Scala代碼,你們能夠先體會一下:程序員

Source.fromFile("D:/f.txt", "UTF-8").getLines().toList.distinct.sortBy(s => (s.charAt(0), s.length)).foreach( println _)

雖然只有一行代碼,可是卻作了不少事情:以UTF-8編碼讀取文件全部行 -> 去重 -> 按首字符排序,首字符相同按長度排序 -> 打印結果。各位腦補一下Java的實現。更多的一行代碼請查看酷炫的一行代碼 - Scala就是這麼任性!。下面咱們進入正題,先看Scala語言簡介。編程

2 Scala簡介

提到編程語言,你們的第一反應一般是面向對象編程(OOP), 然而隨着硬件服務器CPU核數和個數愈來愈多,函數式編程(FP)語言又從新回到了人們的視線。兩種編程語言都各有特色,面向對象編程符合人類對世界的認知,更容易理解;函數式編程的語法更接近人類語言,簡潔高效。兩種語言都讓人沒法取捨。而Scala將這兩種編程語言完美的融合到一塊兒,造成一門更增強大的JVM語言,同時Scala修正了Java不少不合理的設計,新增了更多高級特性,學習Scala的同時也是對Java的一次深度回顧,讓你對編程語言的理解更加地深入。與Java相比,Scala的設計更加一致:api

  • 一切都是對象服務器

    1.toDouble //能夠直接調用基本類型上的方法 "1".toInt //將字符串轉換成整型
  • 一切都是方法併發

    "a" * 3 //等價於: "a".*(3) 2 - 1 //等價於: 2.-(1)
  • 一切都是表達式app

    val i = if(true){ 1 } else { 0 } // i = 1

Scala擁有一套強大的類型推導系統,因此你能夠像動態類型語言那樣編碼,下降代碼冗餘度,減小無心義擊鍵次數同時,代碼也顯得更加清晰易懂。
另外Java和Scala對待程序員的態度也頗有意思,這裏只是開個玩笑,你們別太在乎。Java認爲他所面對的程序員是一幫小白,容易犯錯誤,因此千方百計的限制你,避免你犯錯;而Scala則認爲他所面對的程序員是一幫天才,因此儘量的向他敞開編程語言寶庫,給他更大的自由度去想象和創做。less

3 基本語法規則

3.1 變量聲明

val用於定義不可變變量,var用於定義可變變量,這裏的"可變"指的是引用的可變性。val定義的變量相似於Java的final變量,即變量只能賦一次值:異步

val msg = "hello" // 等價於:val msg: String = "hello" var i = 1 // 等價於:var i: Int = 1 i = i + 1

變量後面的類型聲明能夠省略,每行代碼末尾的分號";"也能夠省略。編程語言

3.2 函數聲明

def用於定義函數:函數式編程

def max(x: Int, y: Int): Int = { if (x > y) { x } else { y } } val maxVal = max(1, 2) // 2

Scala是函數式語言,因此你能夠像基本類型那樣把函數賦給一個變量:

val max = (x: Int, y: Int) => { if (x > y) { x } else { y } } val maxVal = max(1, 2) // 2

等號"="右邊是一個匿名函數,也就是咱們常說的Lambda函數,匿名函數由參數和函數體兩部分組成,中間用"=>"隔開,這裏省略了max變量的類型,由於編譯器能夠自動推斷出來,完整的寫法以下:

val max: (Int, Int) => Int = (x: Int, y: Int) => { if (x > y) { x } else { y } }

max的類型是(Int, Int) => Int,即接受兩個Int參數,產生一個Int返回值的函數類型。

3.3 class

Scala的class定義和Java很類似:

class Counter { private var value = 0 //你必須初始化字段 def increment() { value += 1} //方法默認public def current() = value }

Scala的源文件中能夠定義多個類,而且默認都是public,因此外界均可以看見。class的使用也很簡單:

val myCounter = new Counter //或new Counter() myCounter.increment() println(myCounter.current) //或myCounter.current()

Scala中若是對象方法或類的構造器沒有參數,則括號"()"能夠省略。

3.4 object

Scala沒有靜態方法和靜態字段,而是提供了object對象,也就是Java中的單例對象,即全局只有一個實例。

object Accounts { private var lastNumber = 0 def newUniqueNumber() = { lastNumber += 1; lastNumber } }

由於Accounts是一個單例對象,能夠直接使用而無需初始化:

val uniqueNumber = Accounts.newUniqueNumber

object的另外一個用法是做爲類的伴生對象, 相似於Java類上的靜態方法,只不過Scala將Java類上的靜態功能全交給object實現了。object做爲伴生對象時必須和類在同一個源文件中定義,而且能夠相互訪問私有屬性。

3.5 apply方法

若是某個對象obj上定義了apply方法,則咱們能夠這樣調用:

obj(arg1, ... , argn)

是的,你猜對了,伴生對象上的apply方法立馬就派上用場了,例如List類有一個同名的伴生對象List,那麼你能夠這樣初始化一個列表:

val list = List("a", "b", "c")

想一想下面的Java版本,是否是感受幸福感油然而生:

List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c");

3.6 塊表達式

在Scala中一切都是表達式,若是表達式含有多條語句,則使用大括號"{}"括起來,造成一個塊表達式,塊表達式的最後一條語句的值做爲整個塊的返回值。

val r = {
    val i = 1 val j = 2 i + j } // r = 3

4 case class和模式匹配

在Scala中接觸到新概念不要懼怕,瞭解以後你會發現它幫你解決了不少實際問題,就如咱們這裏要聊的case class和模式匹配。定義一個case class的代碼以下:

case class Currency(value: Double, unit: String)

當你定義了一個case class以後,編譯器會自動幫你作以下事情:

  • 自動建立伴生對象
  • 爲該類添加toString,hashCode和euqals方法,用於模式匹配時的結構化比較
  • 爲該類添加copy方法,用於快速拷貝對象

好了,下面咱們來看一下模式匹配的威力:

abstract class Amount case class Dollar(value: Double) extends Amount case class Currency(value: Double, unit: String) extends Amount val amount = Currency(100.0, "EUR") val amountStr = amount match { case Dollar(v) => "$" + v case Currency(v, u) => "I got " + v + u case _ => "" }

在Scala中,類、函數、方法和object能夠像變量同樣在任何地方定義。

Scala中默認使用的類都是不可變的,因此若是你想改變value的值須要藉助copy方法:

val newAmound = amount.copy(value = 1000.0)

Scala中的模式匹配還能夠實現更復雜的匹配,詳見"Programming in Scala, 3nd Edition"。若是說Java中的switch是一把手槍,那麼Scala中的模式匹配是一架當之無愧的戰頭機。

5 map和flatMap

可能有不少人就是由於這兩個方法才迷戀上Scala的。map和flatMap是兩個高階函數,所謂高階函數就是接受函數做爲參數的函數。這兩個方法各自接受一個一元函數(即只有一個參數的函數,類型爲:(A) => B),利用這個一元函數,你能夠對數據流中的每個元素進行一些操做或轉換,最終獲得一個全新的數據流。
map方法接受的一元函數類型爲:(A) => B:

List(1, 2, 3).map((i: Int) => { i + 1 }) // List(2, 3, 4)

也能夠簡寫以下兩種形式:

List(1, 2, 3).map(i => i + 1 ) List(1, 2, 3).map(_ + 1 )

你能夠把第2種形式中的下劃線理解成每一個元素的佔位符,其實這只是編譯器的語法糖,編譯後的結果和前兩種寫法相同。使用這個語法糖的前提是下劃線"_"在函數體內只能出現一次。

在上面的例子裏,map方法接受的一元函數類型是:(Int) => Int,元素的類型沒有發生改變,咱們能夠嘗試改變元素類型:

List(1, 2, 3).map(i => i.toString * i) // List(1, 22, 333)

此次傳入的一元函數類型是: (Int) => String,將原List從List[Int]類型轉換成了List[String]類型,完成一次數據流類型轉換。

flatMap方法接受的一元函數類型爲:(A) => List[B],咱們發現該一元函數返回的類型也是一個List,flatMap方法會自動將由每一個元素A轉換成的小List[B]展平成一個大的List[B],這也是flatMap中的"flat"所要表達的意思:

List(1, 2, 3).flatMap(i => List(i, i)) // List(1, 1, 2, 2, 3, 3)

這裏咱們只在List上演示了map和flatMap的基本用法,Scala中全部的容器類型(例如Option, Either, Future, Set, ...)都內置了這兩個方法。除了map和flatMap,Scala的容器類型上還有不少相似的方法,例如filter, find, sortBy等等,詳見"Programming in Scala, 3nd Edition"。

6 經常使用類介紹

6.1 String

在Scala中,String更加方便好用:

//原始字符串一對三引號`"""`括起來,可包含多行字符串,內容不須要轉義 """Welcome here. Type "HELP" for help!""" //類型轉換 "100.0".toDouble //判斷字符串相等直接用"==",而不須要使用equals方法 val s1 = new String("a") s1 == "a" // true //字符串去重 "aabbcc".distinct // "abc" //取前n個字符,若是n大於字符串長度返回原字符串 "abcd".take(10) // "abcd" //字符串排序 "bcad".sorted // "abcd" //過濾特定字符 "bcad".filter(_ != 'a') // "bcd" //字符串插值, 以s開頭的字符串內部能夠直接插入變量,方便字符串構造 val i = 100 s"i=${i}" // "i=100"

Scala中沒有受檢異常(checked exception),因此你沒有必要聲明受檢異常,若是真的發生異常,則會在運行時拋出。

6.2 Option

Scala用Option類型表示一個值是否存在,用來避免Java的NullPointerException。它有兩個子類:Some和None。Some類型表示值存在,None類型則表示值不存在。
經常使用操做:

val opt: Option[String] = Some("hello") //判斷是否爲None opt.isEmpty // false //若是爲None,則返回默認值"default",不然返回opt持有的值 opt.getOrElse("default") //若是爲None則返回"DEFAULT",不然將字符轉爲大寫 opt.fold("DEFAULT"){ value => value.toUpperCase } // "HELLO" //功能同上 opt match { case Some(v) => v.toUpperCase case None => "DEFAULT" }

6.3 List

在Scala中,List要麼是Nil(空列表),要麼就是由head和tail組成的遞歸結構。 head是首元素,tail是剩下的List。因此你能夠這樣構建List:

val list = 1 :: Nil // 等價於:val list = List(1)

連續的兩個冒號"::"就像是膠水,將List的head和tail粘在一塊兒。
經常使用操做:

val list = List(1, 3, 2) //獲取第1個元素 list.headOption.getOrElse(0) // 1 //查找 list.find(_ % 2 == 0).getOrElse(0) // 2 //過濾 list.filter(_ % 2 == 1) // List(1, 3) //排序 list.sorted // List(1, 2, 3) //最小值/最大值/求和 list.min // 1 list.max // 3 list.sum // 6 //轉化成字符串 list.mkString(",") // "1, 3, 2"

Scala提供的List基本能夠實現SQL查詢的全部功能,這也是Spark爲何基於Scala開發的緣由。更多功能請參考官方文檔

在Scala中默認的集合類例如List,Set,Map,Tuple等都是不可變的,因此調用其修改方法會返回一個新的實例。若是要使用可變集合,請使用scala.collection.mutable包下相應的類。不可變類型在編寫併發代碼時頗有用。

6.4 Tuple

Tuple(元組)Tuple能夠容納不一樣類型的元素,最簡單的形態是二元組,即由兩個元素構成的Tuple, 可使用_1, _2等方法訪問其元素:

val t = ("a", 1) // 等價於:val t: Tuple2[String, Int] = ("a", 1) t._1 // "a" t._2 // 1

也可使用模式匹配利用Tuple同時初始化一組變量:

val t = ("a", 1) val (v1, v2) = t v1 // "a" v2 // 1

6.5 Map

Map實際上是二元組的集合:

val map = Map("a" -> 1, "b" -> 2)

"->"實際上是String類型上的方法,返回一個二元組:

"a" -> 1 //等價於: ("a", 1)

因此你也能夠這樣構建Map:

val map = Map(("a", 1), ("b", 2))

經常使用操做:

val map = Map("a" -> 1, "b" -> 2) //讀取 map("a") // 1 //寫入或添加鍵值 map("a") = 0 //刪除鍵值 map - "a" // Map(b -> 2)

7 控制結構

7.1 if

if語句一樣是表達式,擁有返回值:

val i = 1 val r = if(i > 0){ 1 } else { 0 } // r = 1

7.2 for

Scala中for語句功能比Java要豐富不少,你可使用for遍歷一個List:

val list = List(1, 2, 3) for(i <- list){ println(i) }

你也可使用模式匹配遍歷一個Map:

val map = Map(("a", 1), ("b", 2)) for((k, v) <- map){ println(k + ": " + v) }

若是循環體以yield開始,for語句會返回一個新的集合:

val newList1 = for(i <- List(1, 2, 3)) yield i * 2 // List(2, 4, 6) val newList2 = for{ i <- List(1, 2) j <- List(3, 4) } yield i + j //List(4, 5, 5, 6)

若是有多個集合須要遍歷,則for語句後面的圓括號"()"要換成大括號"{}"。

8 Future和Promise

Future和Promise是Scala提供的最吸引人的特性之一,藉助Future和Promise你能夠輕鬆地編寫徹底異步非阻塞的代碼,這在多處理器時代顯得格外重要。

8.1 Future

Future用於獲取異步任務的返回結果。Future有兩種狀態:完成(completed)和未完成(not completed)。處於完成狀態的Future可能包含兩種狀況的信息,一種是異步任務執行成功了,Future中包含異步任務執行成功的返回結果;另外一種是異步任務執行失敗了,Future中包含了相應的Exception信息。Future的獨特之處在於它的值只能被寫入一次,以後就會變爲一個不可變值,其中包含成功或失敗信息。你能夠在Future上註冊一個回調函數,以便在任務執行完成後獲得通知:

import scala.concurrent.ExecutionContext.Implicits.global val f = Future{ 1 + 2 } f.onComplete{ t => t match{ case Success(v) => println("success: " + v) case Failure(t) => println("failed: " + t.getMessage) } } //等待任務結束 Await.ready(f, 10 seconds)

onComplete方法接受一個一元函數,類型爲:Try[T] => U。Try類型和Option類型很像,也有兩個子類SuccessFailure,前者表示任務執行成功,後者表示任務執行失敗。

第1行import語句導入了一個隱式的ExecutionContext,你能夠把它理解成是一個線程池,Future類在須要時會自動使用其上的線程。在Scala中你不須要直接和線程打交道。

因爲Future也是一個容器類,因此可使用for語句取回它的值:

val f = Future{ 1 + 2 } for(v <- f) { println(v) // 3 }

也可使用map方法對任務結果進行轉換:

val f1 = Future{ 1 + 2 } val f2 = f1.map(v => v % 2) for(v <- f2) { println(v) // 1 }

利用for語句能夠等待多個Future的返回結果:

val f1 = Future{ 1 + 2 } val f2 = Future{ 3 + 4 } for{ v1 <- f1 v2 <- f2 } { println(v1 + v2) // 10 }

結合yield能夠返回一個新的Future:

val f1 = Future{ 1 + 2 } val f2 = Future{ 3 + 4 } val f3 = for{ v1 <- f1 v2 <- f2 } yield { v1 + v2 }

8.2 Promise

有時咱們須要精細地控制Future的完成時機和返回結果,也就是說咱們須要一個控制Future的開關,沒錯,這個開關就是Promise。每一個Promise實例都會有一個惟一的Future與之相關聯:

val p = Promise[Int]() val f = p.future for(v <- f) { println(v) } //3秒鐘以後返回3 Thread.sleep(3000) p.success(3) //等待任務結束 Await.ready(f, 10 seconds)

9 小結

Scala在剛入門的時候確實有點難度,各類奇怪的語法、符號漫天飛,看的雲裏霧裏。可是在你入門以後會發現,這些奇怪的地方實際上是合理的,是一種有意的設計。例如容許方法名包含特殊符號,你能夠寫出下面的代碼:

"a" * 3 // "aaa" val map = Map("a" -> 1, "b" -> 2)

"*"和"->"實際上是字符串上的兩個方法,容許符號做爲方法名使得代碼直觀易懂。因爲Scala賦予程序員對代碼很高的控制力,若是濫用就會致使天書般的代碼,這須要團隊內部進行協調,控制代碼的複雜度。Scala之父Martin Odersky也曾經表示會在2016簡化Scala語言,下降初學者的門檻。到時會有更多的人加入這個社區,一塊兒分享編程的樂趣。

10 參考

  • "Programming in Scala, 3nd Edition"
  • "快學Scala"

11 附錄

11.1 開發工具推薦

IntelliJ IDEA + Scala插件

11.2 轉載聲明

轉載請註明做者joymufeng

相關文章
相關標籤/搜索