1. 程式人生 > >Java程式設計師的Scala入門教程

Java程式設計師的Scala入門教程

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

安裝Scala

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的強大,除了它自身對多核程式設計更好的支援、函式式特性及一些基於Scala的第3方庫和框架(如:Akka、Playframework、Spark、Kafka……),還在於它可以無縫與Java結合。所有為Java開發的庫、框架都可以自然的融入Scala環境。當然,Scala也可以很方便的Java環境整合,比如:

Spring。若你需要第3方庫的支援,可以使用MavenGradleSbt等編譯環境來引入。

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中,每個類都有