日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

写给Java程序员的Scala入门教程(Java语言程序设计)

發(fā)布時(shí)間:2023/12/15 综合教程 25 生活家
生活随笔 收集整理的這篇文章主要介紹了 写给Java程序员的Scala入门教程(Java语言程序设计) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Java 8擁有了一些初步的函數(shù)式編程能力:閉包等,還有新的并發(fā)編程模型及Stream這個(gè)帶高階函數(shù)和延遲計(jì)算的數(shù)據(jù)集合。在嘗試了Java 8以后,也許會(huì)覺得意猶未盡。是的,你會(huì)發(fā)現(xiàn)Scala能滿足你在初步嘗試函數(shù)式編程后那求知的欲望。

安裝Scala

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

wget -c http://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgztar 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>

登錄后復(fù)制

RELP

剛才我們已經(jīng)啟動(dòng)了Scala RELP,它是一個(gè)基于命令行的交互式編程環(huán)境。對(duì)于有著Python、Ruby等動(dòng)態(tài)語言的同學(xué)來說,這是一個(gè)很常用和工具。但Javaer們第一次見到會(huì)覺得比較神奇。我們可以在RELP中做一些代碼嘗試而不用啟動(dòng)笨拙的IDE,這在我們思考問題時(shí)非常的方便。對(duì)于Javaer有一個(gè)好消息,JDK 9干始將內(nèi)建支持RELP功能。

對(duì)于Scala常用的IDE(集成開發(fā)環(huán)境),推薦使用IDEA for scala plugins和scala-ide。

Scala的強(qiáng)大,除了它自身對(duì)多核編程更好的支持、函數(shù)式特性及一些基于Scala的第3方庫和框架(如:Akka、Playframework、Spark、Kafka……),還在于它可以無縫與Java結(jié)合。所有為Java開發(fā)的庫、框架都可以自然的融入Scala環(huán)境。當(dāng)然,Scala也可以很方便的Java環(huán)境集成,比如:Spring。若你需要第3方庫的支持,可以使用Maven、Gradle、Sbt等編譯環(huán)境來引入。

Scala是一個(gè)面向?qū)ο蟮暮瘮?shù)式特性編程語言,它繼承了Java的面向?qū)μ匦裕瑫r(shí)又從Haskell等其它語言那里吸收了很多函數(shù)式特性并做了增強(qiáng)。

變量、基礎(chǔ)數(shù)據(jù)類型

Scala中變量不需要顯示指定類型,但需要提前聲明。這可以避免很多命名空間污染問題。Scala有一個(gè)很強(qiáng)大的類型自動(dòng)推導(dǎo)功能,它可以根據(jù)右值及上下文自動(dòng)推導(dǎo)出變量的類型。你可以通過如下方式來直接聲明并賦值。

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歲

登錄后復(fù)制

Immutable

(注:函數(shù)式編程有一個(gè)很重要的特性:不可變性。Scala中除了變量的不可變性,它還定義了一套不可變集合scala.collection.immutable._。)

val代表這是一個(gè)final variable,它是一個(gè)常量。定義后就不可以改變,相應(yīng)的,使用var定義的就是平常所見的變量了,是可以改變的。從終端的打印可以看出,Scala從右值自動(dòng)推導(dǎo)出了變量的類型。Scala可以如動(dòng)態(tài)語言似的編寫代碼,但又有靜態(tài)語言的編譯時(shí)檢查。這對(duì)于Java中冗長、重復(fù)的類型聲明來說是一種很好的進(jìn)步。

(注:在RELP中,val變量是可以重新賦值的,這是`RELP`的特性。在平常的代碼中是不可以的。)

基礎(chǔ)數(shù)據(jù)類型

Scala中基礎(chǔ)數(shù)據(jù)類型有:Byte、Short、Int、Long、Float、Double,Boolean,Char、String。和Java不同的是,Scala中沒在區(qū)分原生類型和裝箱類型,如:int和Integer。它統(tǒng)一抽象成Int類型,這樣在Scala中所有類型都是對(duì)象了。編譯器在編譯時(shí)將自動(dòng)決定使用原生類型還是裝箱類型。

字符串

Scala中的字符串有3種。

分別是普通字符串,它的特性和Java字符串一至。

連線3個(gè)雙引號(hào)在Scala中也有特殊含義,它代表被包裹的內(nèi)容是原始字符串,可以不需要字符轉(zhuǎn)碼。這一特性在定義正則表達(dá)式時(shí)很有優(yōu)勢(shì)。

還有一種被稱為“字符串插值”的字符串,他可以直接引用上下文中的變量,并把結(jié)果插入字符串中。

scala> val c2 = '楊'
c2: Char = 楊

scala> val s1 = "重慶譽(yù)存企業(yè)信用管理有限公司"
s1: String = 重慶譽(yù)存企業(yè)信用管理有限公司

scala> val s2 = s"重慶譽(yù)存企業(yè)信用管理有限公司${c2}景"
s2: String = 重慶譽(yù)存企業(yè)信用管理有限公司

scala> val s3 = s"""重慶譽(yù)存企業(yè)信用管理有限公司"工程師"\n${c2}景是江津人"""
s3: String =
重慶譽(yù)存企業(yè)信用管理有限公司"工程師"
楊景是江津人

登錄后復(fù)制

運(yùn)算符和命名

Scala中的運(yùn)算符其實(shí)是定義在對(duì)象上的方法(函數(shù)),你看到的諸如:3 + 2其實(shí)是這樣子的:3.+(2)。+符號(hào)是定義在Int對(duì)象上的一個(gè)方法。支持和Java一至的運(yùn)算符(方法):

(注:在Scala中,方法前的.號(hào)和方法兩邊的小括號(hào)在不引起歧義的情況下是可以省略的。這樣我們就可以定義出很優(yōu)美的DSL)

==、!=:比較運(yùn)算

!、|、&、^:邏輯運(yùn)算

>>、<<:位運(yùn)算

注意

在Scala中,修正了(算更符合一般人的常規(guī)理解吧)==和!=運(yùn)算符的含義。在Scala中,==和!=是執(zhí)行對(duì)象的值比較,相當(dāng)于Java中的equals方法(實(shí)際上編譯器在編譯時(shí)也是這么做的)。而對(duì)象的引用比較需要使用eq和ne兩個(gè)方法來實(shí)現(xiàn)。

控制語句(表達(dá)式)

Scala中支持if、while、for comprehension(for表達(dá)式)、match case(模式匹配)四大主要控制語句。Scala不支持switch和? :兩種控制語句,但它的if和match case會(huì)有更好的實(shí)現(xiàn)。

if

Scala支持if語句,其基本使用和Java、Python中的一樣。但不同的時(shí),它是有返回值的。

(注:Scala是函數(shù)式語言,函數(shù)式語言還有一大特性就是:表達(dá)式。函數(shù)式語言中所有語句都是基于“表達(dá)式”的,而“表達(dá)式”的一個(gè)特性就是它會(huì)有一個(gè)值。所有像Java中的? :3目運(yùn)算符可以使用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 = 真

登錄后復(fù)制

可以看到,if語句也是有返回值的,將表達(dá)式的結(jié)果賦給變量,編譯器也能正常推導(dǎo)出變量的類型。unit和unit2變量的類型是Any,這是因?yàn)閑lse語句的缺失,Scala編譯器就按最大化類型來推導(dǎo),而Any類型是Scala中的根類型。()在Scala中是Unit類型的實(shí)例,可以看做是Java中的Void。

while

Scala中的while循環(huán)語句:

while (條件) {
  語句塊
}

登錄后復(fù)制

for comprehension

Scala中也有for表達(dá)式,但它和Java中的for不太一樣,它具有更強(qiáng)大的特性。通常的for語句如下:

for (變量 <- 集合) {
  語句塊
}

登錄后復(fù)制

Scala中for表達(dá)式除了上面那樣的常規(guī)用法,它還可以使用yield關(guān)鍵字將集合映射為另一個(gè)集合:

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 + 1list2: List[Int] = List(2, 3, 4, 5, 6)

登錄后復(fù)制

還可以在表達(dá)式中使用if判斷:

scala> val list3 = for (item <- list if item % 2 == 0) yield item
list3: List[Int] = List(2, 4)

登錄后復(fù)制

還可以做flatMap操作,解析2維列表并將結(jié)果攤平(將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)

登錄后復(fù)制

看到了,Scala中for comprehension的特性是很強(qiáng)大的。Scala的整個(gè)集合庫都支持這一特性,包括:Seq、Map、Set、Array……

Scala沒有C-Like語言里的for (int i = 0; i < 10; i++)語法,但Range(范圍這個(gè)概念),可以基于它來實(shí)現(xiàn)循環(huán)迭代功能。在Scala中的使用方式如下:

scala> for (i <- (0 until 10)) {
     |   println(i)
     | }
0
1
2
3
4
5
6
7
8
9

登錄后復(fù)制

Scala中還有一個(gè)to方法:

scala> for (i <- (0 to 10))
 print(" " + i) 0 1 2 3 4 5 6 7 8 9 10

登錄后復(fù)制

match case

模式匹配,是函數(shù)式語言很強(qiáng)大的一個(gè)特性。它比命令式語言里的switch更好用,表達(dá)性更強(qiáng)。

scala> def level(s: Int) = s match {
     |   case n if n >= 90 => "優(yōu)秀"
     |   case n if n >= 80 => "良好"
     |   case n if n >= 70 => "良"
     |   case n if n >= 60 => "及格"
     |   case _ => "差"
     | }level: (s: Int)Stringscala> level(51)res28: String = 差

scala> level(93)res29: String = 優(yōu)秀

scala> level(80)res30: String = 良好

登錄后復(fù)制

可以看到,模式匹配可以實(shí)現(xiàn)switch相似的功能。但與switch需要使用break明確告知終止之后的判斷不同,Scala中的match case是默認(rèn)break的。只要其中一個(gè)case語句匹配,就終止之后的所以比較。且對(duì)應(yīng)case語句的表達(dá)式值將作為整個(gè)match case表達(dá)式的值返回。

Scala中的模式匹配還有類型匹配、數(shù)據(jù)抽取、謂詞判斷等其它有用的功能。這里只做簡單介紹,之后會(huì)單獨(dú)一個(gè)章節(jié)來做較詳細(xì)的解讀。

集合

在java.util包下有豐富的集合庫。Scala除了可以使用Java定義的集合庫外,它還自己定義了一套功能強(qiáng)大、特性豐富的scala.collection集合庫API。

在Scala中,常用的集合類型有:List、Set、Map、Tuple、Vector等。

List

Scala中List是一個(gè)不可變列表集合,它很精妙的使用遞歸結(jié)構(gòu)定義了一個(gè)列表集合。

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

登錄后復(fù)制

除了之前使用Listobject來定義一個(gè)列表,還可以使用如下方式:

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

登錄后復(fù)制

List采用前綴操作的方式(所有操作都在列表頂端(開頭))進(jìn)行,::操作符的作用是將一個(gè)元素和列表連接起來,并把元素放在列表的開頭。這樣List的操作就可以定義成一個(gè)遞歸操作。添加一個(gè)元素就是把元素加到列表的開頭,List只需要更改下頭指針,而刪除一個(gè)元素就是把List的頭指針指向列表中的第2個(gè)元素。這樣,List的實(shí)現(xiàn)就非常的高效,它也不需要對(duì)內(nèi)存做任何的轉(zhuǎn)移操作。List有很多常用的方法:

scala> list.indexOf(3)
res6: Int = 2scala> 0 :: listres8: 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> listres12: 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)

登錄后復(fù)制

Scala中默認(rèn)都是Immutable collection,在集合上定義的操作都不會(huì)更改集合本身,而是生成一個(gè)新的集合。這與Java集合是一個(gè)根本的區(qū)別,Java集合默認(rèn)都是可變的。

Tuple

Scala中也支持Tuple(元組)這種集合,但最多只支持22個(gè)元素(事實(shí)上Scala中定義了Tuple0、Tuple1……Tuple22這樣22個(gè)TupleX類,實(shí)現(xiàn)方式與C++ Boost庫中的Tuple類似)。和大多數(shù)語言的Tuple類似(比如:Python),Scala也采用小括號(hào)來定義元組。

scala> val tuple1 = (1, 2, 3)tuple1: (Int, Int, Int) = (1,2,3)

scala> tuple1._2res17: Int = 2scala> val tuple2 = Tuple2("楊", " )
tuple2: (String, String) = (楊,景)

登錄后復(fù)制

可以使用xxx._[X]的形式來引用Tuple中某一個(gè)具體元素,其_[X]下標(biāo)是從1開始的,一直到22(若有定義這么多)。

Set

Set是一個(gè)不重復(fù)且無序的集合,初始化一個(gè)Set需要使用Set對(duì)象:

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)

登錄后復(fù)制

Map

Scala中的Map默認(rèn)是一個(gè)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)

登錄后復(fù)制

Scala中定義Map時(shí),傳入的每個(gè)Entry(K、V對(duì))其實(shí)就是一個(gè)Tuple2(有兩個(gè)元素的元組),而->是定義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)

登錄后復(fù)制

Scala的immutable collection并沒有添加和刪除元素的操作,其定義+(List使用::在頭部添加)操作都是生成一個(gè)新的集合,而要?jiǎng)h除一個(gè)元素一般使用 - 操作直接將Key從map中減掉即可。

(注:Scala中也scala.collection.mutable._集合,它定義了不可變集合的相應(yīng)可變集合版本。一般情況下,除非一此性能優(yōu)先的操作(其實(shí)Scala集合采用了共享存儲(chǔ)的優(yōu)化,生成一個(gè)新集合并不會(huì)生成所有元素的復(fù)本,它將會(huì)和老的集合共享大元素。因?yàn)镾cala中變量默認(rèn)都是不可變的),推薦還是采用不可變集合。因?yàn)樗庇^、線程安全,你可以確定你的變量不會(huì)在其它地方被不小心的更改。)

Class

Scala里也有class關(guān)鍵字,不過它定義類的方式與Java有些區(qū)別。Scala中,類默認(rèn)是public的,且類屬性和方法默認(rèn)也是public的。Scala中,每個(gè)類都有一個(gè)“主構(gòu)造函數(shù)”,主構(gòu)造函數(shù)類似函數(shù)參數(shù)一樣寫在類名后的小括號(hào)中。因?yàn)镾cala沒有像Java那樣的“構(gòu)造函數(shù)”,所以屬性變量都會(huì)在類被創(chuàng)建后初始化。所以當(dāng)你需要在構(gòu)造函數(shù)里初始化某些屬性或資源時(shí),寫在類中的屬性變量就相當(dāng)于構(gòu)造初始化了。

在Scala中定義類非常簡單:

class Person(name: String, val age: Int) {
  override def toString(): String = s"姓名:$name, 年齡: $age"
}

登錄后復(fù)制

默認(rèn),Scala主構(gòu)造函數(shù)定義的屬性是private的,可以顯示指定:val或var來使其可見性為:public。

Scala中覆寫一個(gè)方法必需添加:override關(guān)鍵字,這對(duì)于Java來說可以是一個(gè)修正。當(dāng)標(biāo)記了override關(guān)鍵字的方法在編譯時(shí),若編譯器未能在父類中找到可覆寫的方法時(shí)會(huì)報(bào)錯(cuò)。而在Java中,你只能通過@Override注解來實(shí)現(xiàn)類似功能,它的問題是它只是一個(gè)可選項(xiàng),且編譯器只提供警告。這樣你還是很容易寫出錯(cuò)誤的“覆寫”方法,你以后覆寫了父類函數(shù),但其實(shí)很有可能你是實(shí)現(xiàn)了一個(gè)新的方法,從而引入難以察覺的BUG。

實(shí)例化一個(gè)類的方式和Java一樣,也是使用new關(guān)鍵字。

scala> val me = new Person("楊景", 30)
me: Person = 姓名:楊景, 年齡: 30scala> println(me)
姓名:楊景, 年齡: 30scala> me.name
<console>:20: error: value name is not a member of Person
       me.name
          ^

scala> me.ageres11: Int = 30

登錄后復(fù)制

case class(樣本類)

case class是Scala中學(xué)用的一個(gè)特性,像Kotlin這樣的語言也學(xué)習(xí)并引入了類似特性(在Kotlin中叫做:data class)。case class具有如下特性:

不需要使用new關(guān)鍵詞創(chuàng)建,直接使用類名即可

默認(rèn)變量都是public final的,不可變的。當(dāng)然也可以顯示指定var、private等特性,但一般不推薦這樣用

自動(dòng)實(shí)現(xiàn)了:equals、hashcode、toString等函數(shù)

自動(dòng)實(shí)現(xiàn)了:Serializable接口,默認(rèn)是可序列化的

可應(yīng)用到match case(模式匹配)中

自帶一個(gè)copy方法,可以方便的根據(jù)某個(gè)case class實(shí)例來生成一個(gè)新的實(shí)例

……

這里給出一個(gè)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)

登錄后復(fù)制

object

Scala有一種不同于Java的特殊類型,Singleton Objects。

object Blah {  def sum
(l: List[Int]): Int = l.sum
}

登錄后復(fù)制

在Scala中,沒有Java里的static靜態(tài)變量和靜態(tài)作用域的概念,取而代之的是:object。它除了可以實(shí)現(xiàn)Java里static的功能,它同時(shí)還是一個(gè)線程安全的單例類。

伴身對(duì)象

大多數(shù)的object都不是獨(dú)立的,通常它都會(huì)與一個(gè)同名的class定義在一起。這樣的object稱為伴身對(duì)象。

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))
}

登錄后復(fù)制

注意

伴身對(duì)象必需和它關(guān)聯(lián)的類定義定義在同一個(gè).scala文件。

伴身對(duì)象和它相關(guān)的類之間可以相互訪問受保護(hù)的成員。在Java程序中,很多時(shí)候會(huì)把static成員設(shè)置成private的,在Scala中需要這樣實(shí)現(xiàn)此特性:

class X {
  import X._
  def blah = foo
}
object X {
  private def foo = 42
}

登錄后復(fù)制

函數(shù)

在Scala中,函數(shù)是一等公民。函數(shù)可以像類型一樣被賦值給一個(gè)變量,也可以做為一個(gè)函數(shù)的參數(shù)被傳入,甚至還可以做為函數(shù)的返回值返回。

從Java 8開始,Java也具備了部分函數(shù)式編程特性。其Lamdba函數(shù)允許將一個(gè)函數(shù)做值賦給變量、做為方法參數(shù)、做為函數(shù)返回值。

在Scala中,使用def關(guān)鍵ygnk來定義一個(gè)函數(shù)方法:

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

登錄后復(fù)制

這里定義了一個(gè)函數(shù):calc,它有兩個(gè)參數(shù):n1和n2,其類型為:Int。cala函數(shù)的返回值類型是一個(gè)有兩個(gè)元素的元組,在Scala中可以簡寫為:(Int, Int)。在Scala中,代碼段的最后一句將做為函數(shù)返回值,所以這里不需要顯示的寫return關(guān)鍵字。

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

函數(shù)可以賦給變量:

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

登錄后復(fù)制

在Scala中,有兩種定義函數(shù)的方式:

將一個(gè)現(xiàn)成的函數(shù)/方法賦值給一個(gè)變量,如:val calcVar = calc _。下劃線在此處的含意是將函數(shù)賦給了變量,函數(shù)本身的參數(shù)將在變量被調(diào)用時(shí)再傳入。

直接定義函數(shù)并同時(shí)賦給變量,如:val sum: (Int, Int) => Int = (x, y) => x + y,在冒號(hào)之后,等號(hào)之前部分:(Int, Int) => Int是函數(shù)簽名,代表sum這個(gè)函數(shù)值接收兩個(gè)Int類型參數(shù)并返回一個(gè)Int類型參數(shù)。等號(hào)之后部分是函數(shù)體,在函數(shù)函數(shù)時(shí),x、y參數(shù)類型及返回值類型在此可以省略。

一個(gè)函數(shù)示例:自動(dòng)資源管理

在我們的日常代碼中,資源回收是一個(gè)很常見的操作。在Java 7之前,我們必需寫很多的try { ... } finally { xxx.close() }這樣的樣版代碼來手動(dòng)回收資源。Java 7開始,提供了try with close這樣的自動(dòng)資源回收功能。Scala并不能使用Java 7新加的try with close資源自動(dòng)回收功能,但Scala中有很方便的方式實(shí)現(xiàn)類似功能:

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)

登錄后復(fù)制

using是我們定義的一個(gè)自動(dòng)化資源管幫助函數(shù),它接愛兩個(gè)參數(shù)化類型參數(shù),一個(gè)是實(shí)現(xiàn)了AutoCloseable接口的資源類,一個(gè)是形如:T => R的函數(shù)值。func是由用戶定義的對(duì)res進(jìn)行操作的函數(shù)代碼體,它將被傳給using函數(shù)并由using代執(zhí)行。而res這個(gè)資源將在using執(zhí)行完成返回前調(diào)用finally代碼塊執(zhí)行.close方法來清理打開的資源。

這個(gè):T <: AutoCloseable范型參數(shù)限制了T類型必需為AutoCloseable類型或其子類。R范型指定using函數(shù)的返回值類型將在實(shí)際調(diào)用時(shí)被自動(dòng)參數(shù)化推導(dǎo)出來。我們?cè)赟cala Console中參看allLine變量的類型可以看到 allLine將被正確的賦予String類型,因?yàn)槲覀儌鹘ousing函數(shù)參數(shù)func的函數(shù)值返回類型就為String:

scala> :type allLineString

登錄后復(fù)制

在readAll函數(shù)的定義處,有兩個(gè)特別的地方:

這個(gè)函數(shù)定義在了其它函數(shù)代碼體內(nèi)部

它有一個(gè)@tailrec注解

在Scala中,因?yàn)楹瘮?shù)是第一類的,它可以被賦值給一個(gè)變量。所以Scala中的def定義函數(shù)可以等價(jià)val func = (x: Int, y: Int) => x + y這個(gè)的函數(shù)字面量定義函數(shù)形式。所以,既然通過變量定義的函數(shù)可以放在其它函數(shù)代碼體內(nèi),通過def定義的函數(shù)也一樣可以放在其它代碼體內(nèi),這和Javascript很像。

@tailrec注解的含義是這個(gè)函數(shù)是尾遞歸函數(shù),編譯器在編譯時(shí)將對(duì)其優(yōu)化成相應(yīng)的while循環(huán)。若一個(gè)函數(shù)不是尾遞歸的,加上此注解在編譯時(shí)將報(bào)錯(cuò)。

模式匹配(match case)

模式匹配是函數(shù)式編程里面很強(qiáng)大的一個(gè)特性。

之前已經(jīng)見識(shí)過了模式匹配的簡單使用方式,可以用它替代:if else、switch這樣的分支判斷。除了這些簡單的功能,模式匹配還有一系列強(qiáng)大、易用的特性。

match 中的值、變量和類型

scala> for {
     |   x <- Seq(1, false, 2.7, "one", 'four, new java.util.Date(), new RuntimeException("運(yùn)行時(shí)異常"))
     | } {
     |   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: 運(yùn)行時(shí)異常

登錄后復(fù)制

上面小試牛刀校驗(yàn)變量類型的同時(shí)完成類型轉(zhuǎn)換功能。在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;
        ....
    } ....
}

登錄后復(fù)制

對(duì)于這樣的代碼,真是辣眼睛啊~~~。

序列的匹配

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

登錄后復(fù)制

模式匹配能很方便的抽取序列的元素,seqToString使用了模式匹配以遞歸的方式來將序列轉(zhuǎn)換成字符串。case head +: tail將序列抽取成“頭部”和“非頭部剩下”兩部分,head將保存序列第一個(gè)元素,tail保存序列剩下部分。而case Nil將匹配一個(gè)空序列。

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歲

登錄后復(fù)制

在模式匹配中對(duì)case class進(jìn)行解構(gòu)操作,可以直接提取出感興趣的字段并賦給變量。同時(shí),模式匹配中還可以使用guard語句,給匹配判斷添加一個(gè)if表達(dá)式做條件判斷。

并發(fā)

Scala是對(duì)多核和并發(fā)編程的支付做得非常好,它的Future類型提供了執(zhí)行異步操作的高級(jí)封裝。

Future對(duì)象完成構(gòu)建工作以后,控制權(quán)便會(huì)立刻返還給調(diào)用者,這時(shí)結(jié)果還不可以立刻可用。Future實(shí)例是一個(gè)句柄,它指向最終可用的結(jié)果值。不論操作成功與否,在future操作執(zhí)行完成前,代碼都可以繼續(xù)執(zhí)行而不被阻塞。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

登錄后復(fù)制

上面代碼創(chuàng)建了10個(gè)Future對(duì)象,F(xiàn)uture.apply方法有兩個(gè)參數(shù)列表。第一個(gè)參數(shù)列表包含一個(gè)需要并發(fā)執(zhí)行的命名方法體(by-name body);而第二個(gè)參數(shù)列表包含了隱式的ExecutionContext對(duì)象,可以簡單的把它看作一個(gè)線程池對(duì)象,它決定了這個(gè)任務(wù)將在哪個(gè)異步(線程)執(zhí)行器中執(zhí)行。futures對(duì)象的類型為IndexedSeq[Future[String]]。本示例中使用Future.reduce把一個(gè)futures的IndexedSeq[Future[String]]類型壓縮成單獨(dú)的Future[String]類型對(duì)象。Await.result用來阻塞代碼并獲取結(jié)果,輸入的Duration.Inf用于設(shè)置超時(shí)時(shí)間,這里是無限制。

這里可以看到,在Future代碼內(nèi)部的println語句打印輸出是無序的,但最終獲取的result結(jié)果卻是有序的。這是因?yàn)殡m然每個(gè)Future都是在線程中無序執(zhí)行,但Future.reduce方法將按傳入的序列順序合并結(jié)果。

除了使用Await.result阻塞代碼獲取結(jié)果,我們還可以使用事件回調(diào)的方式異步獲取結(jié)果。Future對(duì)象提供了幾個(gè)方法通過回調(diào)將執(zhí)行的結(jié)果返還給調(diào)用者,常用的有:

onComplete: PartialFunction[Try[T], Unit]:當(dāng)任務(wù)執(zhí)行完成后調(diào)用,無論成功還是失敗

onSuccess: PartialFunction[T, Unit]:當(dāng)任務(wù)成功執(zhí)行完成后調(diào)用

onFailure: PartialFunction[Throwable, Unit]:當(dāng)任務(wù)執(zhí)行失敗(異常)時(shí)調(diào)用

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是奇數(shù)")
  case 2 => Future.failed(new RuntimeException("2不是奇數(shù)"))
}

futures.foreach(_.onComplete {
  case Success(i) => println(i)
  case Failure(t) => println(t)
})

Thread.sleep(2000)

登錄后復(fù)制

futures.onComplete方法是一個(gè)偏函數(shù),它的參數(shù)是:Try[String]。Try有兩個(gè)子類,成功是返回Success[String],失敗時(shí)返回Failure[Throwable],可以通過模式匹配的方式獲取這個(gè)結(jié)果。

總結(jié)

本篇文章簡單的介紹了Scala的語言特性,本文并不只限于Java程序員,任何有編程經(jīng)驗(yàn)的程序員都可以看。現(xiàn)在你應(yīng)該對(duì)Scala有了一個(gè)基礎(chǔ)的認(rèn)識(shí),并可以寫一些簡單的代碼了。

總結(jié)

以上是生活随笔為你收集整理的写给Java程序员的Scala入门教程(Java语言程序设计)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。