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

来源:http://www.cnblogs.com/joymufeng/archive/2017/05/16/6860857.html
-Advertisement-
Play Games

1 前言 本文的主要目的是為了讓Play Framework的初學者快速瞭解Scala語言,算是一篇Play Framework的入門前傳吧。使用PlayFramework可以極大的提高開發效率,但是需要註意,PlayJava入門很簡單,我之前帶過一個實習小姑娘,有一點編程經驗,但從來沒有接觸過Pl ...


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的設計更加一致:

  • 一切都是對象

    1.toDouble  //可以直接調用基本類型上的方法
    "1".toInt //將字元串轉換成整型
  • 一切都是方法

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

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

Scala擁有一套強大的類型推導系統,所以你可以像動態類型語言那樣編碼,降低代碼冗餘度,減少無意義擊鍵次數同時,代碼也顯得更加清晰易懂。
另外Java和Scala對待程式員的態度也很有意思,這裡只是開個玩笑,大家別太在意。Java認為他所面對的程式員是一幫小白,容易犯錯誤,所以想方設法的限制你,避免你犯錯;而Scala則認為他所面對的程式員是一幫天才,所以儘可能的向他敞開編程語言寶庫,給他更大的自由度去想象和創作。

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 變數 在程式設計中,變數(英語:Variable,scalar)是指一個包含部分已知或未知數值或資訊(即一個值)之儲存位址,以及相對應之符號名稱(識別字)。通常使用變數名稱參照儲存值;將名稱和內容分開能讓被使用的名稱獨立於所表示的精確訊息之外。電腦原始碼中的識別字能在執行期間綁扎一個值,且該變數的值 ...
  • 一、場景 之前做的電商平臺,用戶在收到貨之後,大部分都不會主動的點擊確認收貨,導致給商家結款的時候,商家各種投訴,於是就根據需求,要做一個訂單在發貨之後的x天自動確認收貨。所謂的訂單自動確認收貨,就是在在特定的時間,執行一條update語句,改變訂單的狀態。 二、思路 最笨重的做法,通過linux後 ...
  • XML 使用DTD(document type definition)文檔類型來標記數據和定義數據,格式統一且跨平臺和語言,已成為業界公認的標準。 目前 XML 描述數據龍頭老大的地位漸漸受到 Json 威脅。經手項目中,模塊/系統之間交互數據方式有 XML 也有 Json,說不上孰好孰壞。 XML ...
  • 題目描述 以下皆為真實的故事。 洛谷2的團隊功能是其他任何oj和工具難以達到的。藉助洛谷強大的伺服器資源,任何學校都可以在洛谷上零成本的搭建oj並高效率的完成訓練計劃。 為什麼說是搭建oj呢?為什麼高效呢? 因為,你可以上傳私有題目,團隊外別人是無法看到的。我們還能幫你們評測! 你可以創建作業,給組 ...
  • 轉載連接:http://likfe.com/2017/05/10/android sys language/ 前言 獲取系統當前語言是一個比較常用的功能,在 Android 7.0 系統上舊函數獲取到的當前系統語言並不正確,或者說從 Android 7.0 起,Android 系統語言的規則變了。 ...
  • <span style="font-size:16px"></span><h3><span style="font-family:宋體,arial,sans-serif;font-size:14px;line-height:25px"><span class="link_title"><span s ...
  • 最近準備複習一遍所有的知識點,先從基礎開始做起,用幾分鐘寫個繼承和析構吧。 父類為A,子類為B,代碼如下: 這個時候在main函數里, 定義一個A的對象,A a;運行輸出結果為:構造A 析構A。 定義一個B的對象, B b;運行的輸出結果為:構造B 析構B。 定義一個A的指針,指向B。A *P = ...
  • 這是關於Python的第11篇文章,主要介紹下數據結構的3個小技巧。 排序: 使用sorted函數實現排序。 sorted函數按照長短、大小、英文字母的順序給每個列表的元素進行排序。這個函數經常在數據展示中使用,其中很重要的一點是sorted函數不會改變列表本身,相當於先複製列表然後做排序整理。 推 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...