進一步 Scala

2020-07-09 10:19 更新

進一步 Scala

本篇繼續(xù)上一篇對 Scala 的整體介紹,本篇進一步解釋 Scala 的一些高級特性,當你學完本篇后,就有足夠的知識編寫一些實用的 Scala 腳本應用了。

第七步:使用類型參數(shù)化數(shù)組

在 Scala 中你可以使用 new 來實例化一個類。當你創(chuàng)建一個對象的實例時,你可以使用數(shù)值或類型參數(shù)。如果使用類型參數(shù),它的作用類似 Java 或 .Net 的 Generic 類型。不同的是 Scala 使用方括號來指明數(shù)據(jù)類型參數(shù),而非尖括號。比如

val greetStrings =new Array[String](3)
greetStrings(0)="Hello"
greetStrings(1)=","
greetStrings(2)="world!\n"
for(i <- 0 to 2)
  print(greetStrings(i))

可以看到 Scala 使用?[]?來為數(shù)組指明類型化參數(shù),本例使用 ?String? 類型,數(shù)組使用?()?而非?[]?來指明數(shù)組的索引。其中的 ?for? 表達式中使用到 ?0 to 2? ,這個表達式演示了 Scala 的一個基本規(guī)則,如果一個方法只有一個參數(shù),你可以不用括號和?.? 來調(diào)用這個方法。因此這里的 ?0 to 2?, 其實為?(0).to(2)? 調(diào)用的為整數(shù)類型的 ?to? 方法,?to? 方法使用一個參數(shù)。

Scala 中所有的基本數(shù)據(jù)類型也是對象(和 Java 不同),因此 0 可以有方法(實際上調(diào)用的是 ?RichInt? 的方法),這種只有一個參數(shù)的方法可以使用操作符的寫法(不用?.?和括號),實際上 Scala 中表達式 1+2,最終解釋為 ?(1).+(2) +? 也是 ?Int? 的一個方法,和 Java 不同的是,Scala 對方法的名稱沒有太多的限制,你可以使用符合作為方法的名稱。

這里也說明為什么 Scala 中使用?()?來訪問數(shù)組元素,在 Scala 中,數(shù)組和其它普遍的類定義一樣,沒有什么特別之處,當你在某個值后面使用?()?時,Scala 將其翻譯成對應對象的 ?apply? 方法。因此本例中 ?greetStrings(1)? 其實調(diào)用 ?greetString.apply(1)? 方法。這種表達方法不僅僅只限于數(shù)組,對于任何對象,如果在其后面使用?()?,都將調(diào)用該對象的 ?apply? 方法。同樣的,如果對某個使用?()?的對象賦值,比如:

greetStrings(0)="Hello"

Scala 將這種賦值轉換為該對象的 ?update? 方法, 也就是 ?greetStrings.update(0,”hello”)?。因此上面的例子,使用傳統(tǒng)的方法調(diào)用可以寫成:

val greetStrings =new Array[String](3)
greetStrings.update(0,"Hello")
greetStrings.update(1,",")
greetStrings.update(2,"world!\n")
for(i <- 0 to 2)
  print(greetStrings.apply(i))

從這點來說,數(shù)組在 Scala 中并不某種特殊的數(shù)據(jù)類型,和普通的類沒有什么不同。

不過 Scala 還是提供了初始化數(shù)組的簡單的方法,比如什么的例子數(shù)組可以使用如下代碼:

val greetStrings =Array("Hello",",","World\n")

這里使用?()?其實還是調(diào)用 ?Array? 類的關聯(lián)對象 ?Array? 的 ?apply ?方法,也就是

val greetStrings =Array.apply("Hello",",","World\n")

第八步: 使用Lists

Scala 也是一個面向函數(shù)的編程語言,面向函數(shù)的編程語言的一個特點是,調(diào)用某個方法不應該有任何副作用,參數(shù)一定,調(diào)用該方法后,返回一定的結果,而不會去修改程序的其它狀態(tài)(副作用)。這樣做的一個好處是方法和方法之間關聯(lián)性較小,從而方法變得更可靠和重用性高。使用這個原則也就意味著就變量的設成不可修改的,這也就避免了多線程訪問的互鎖問題。

前面介紹的數(shù)組,它的元素是可以被修改的。如果需要使用不可以修改的序列,Scala 中提供了 ?Lists? 類。和 Java 的 ?List? 不同,Scala 的? Lists ?對象是不可修改的。它被設計用來滿足函數(shù)編程風格的代碼。它有點像 Java 的 ?String?,?String? 也是不可以修改的,如果需要可以修改的 ?String? 對像,可以使用 ?StringBuilder? 類。

比如下面的代碼:

val oneTwo = List(1,2)
val threeFour = List(3,4)
val oneTwoThreeFour=oneTwo ::: threeFour
println (oneTwo + " and " + threeFour + " were not mutated.")
println ("Thus, " + oneTwoThreeFour + " is a new list")

定義了兩個 List 對象 oneTwo 和 threeFour,然后通過?:::?操作符(其實為?:::?方法)將兩個列表鏈接起來。實際上由于? List ?的不可修改特性,Scala 創(chuàng)建了一個新的 ?List? 對象 ?oneTwo??ThreeFour ?來保存兩個列表連接后的值。

?List? 也提供了一個?::?方法用來向? List ?中添加一個元素,?::?方法(操作符)是右操作符,也就是使用?::?右邊的對象來調(diào)用它的?::?方法,Scala 中規(guī)定所有以??開頭的操作符都是右操作符,因此如果你自己定義以??開頭的方法(操作符)也是右操作符。

如下面使用常量創(chuàng)建一個列表:

val oneTowThree = 1 :: 2 ::3 :: Nil
println(oneTowThree)

調(diào)用空列表對象 ?Nil ?的? ::?方法 也就是

val oneTowThree =  Nil.::(3).::(2).::(1)

Scala 的 ?List? 類還定義其它很多很有用的方法,比如 head, last,length, reverse,tail 等這里就不逐一說明了,具體可以參考 ?List? 的文檔。

第九步:使用元組( Tuples )

Scala 中另外一個很有用的容器類為 ?Tuples?,和 ?List ?不同的是 ?Tuples? 可以包含不同類型的數(shù)據(jù),而 ?List? 只能包含同類型的數(shù)據(jù)。Tuples 在方法需要返回多個結果時非常有用。( Tuple 對應到數(shù)學的矢量的概念)。

一但定義了一個元組,可以使用 ?._?和索引來訪問員組的元素(矢量的分量,注意和數(shù)組不同的是,元組的索引從 1 開始)。

val pair=(99,"Luftballons")
println(pair._1)
println(pair._2)

元祖的實際類型取決于它的分量的類型,比如上面 ?pair? 的類型實際為 ?Tuple2[Int,String]?,而 (‘u’,’r’,”the”,1,4,”me”) 的類型為 ?Tuple6[Char,Char,String,Int,Int,String]?。

目前 Scala 支持的元祖的最大長度為 22。如果有需要,你可以自己擴展更長的元祖。

第十步: 使用 Sets 和 Maps

Scala 語言的一個設計目標是讓程序員可以同時利用面向對象和面向函數(shù)的方法編寫代碼,因此它提供的集合類分成了可以修改的集合類和不可以修改的集合類兩大類型。比如 ?Array? 總是可以修改內(nèi)容的,而 ?List? 總是不可以修改內(nèi)容的。類似的情況,Scala 也提供了兩種 ?Sets? 和 ?Map? 集合類。

比如 Scala API 定義了 ?Set? 的 ?Trait? 類型 ?Set?( ?Trait? 的概念類似于 Java 中的 ?Interface?,所不同的 Scala 中的 ?Trait? 可以有方法實現(xiàn)),分兩個包定義 ?Mutable? (可變)和 ?Immutable ?(不可變),使用同樣名稱的子 ?Trait?。下圖為 ?Trait? 和類的基礎關系:

使用 ?Set? 的基本方法如下:

var jetSet = Set ("Boeing","Airbus")
jetSet +="Lear"
println(jetSet.contains("Cessna"))

缺省情況 ?Set? 為 ?Immutable Set?,如果你需要使用可修改的集合類( Set 類型),你可以使用全路徑來指明 ?Set?,比如 scala.collection.mutalbe.Set 。

Scala 提供的另外一個類型為 ?Map? 類型,Scala 也提供了? Mutable? 和? Immutable? 兩種 ?Map? 類型。

?Map? 的基本用法如下( ?Map? 類似于其它語言中的關聯(lián)數(shù)組如 PHP )

val romanNumeral = Map ( 1 -> "I" , 2 -> "II",
  3 -> "III", 4 -> "IV", 5 -> "V")
println (romanNumeral(4))

第十一步: 學習識別函數(shù)編程風格

Scala 語言的一個特點是支持面向函數(shù)編程,因此學習 Scala 的一個重點是改變之前的指令式編程思想(尤其是來自 Java 或 C# 背景的程序員),觀念要向函數(shù)式編程轉變。首先在看代碼上要認識哪種是指令編程,哪種是函數(shù)式編程。實現(xiàn)這種思想上的轉變,不僅僅會使你成為一個更好的 Scala 程序員,同時也會擴展你的視野,使你成為一個更好的程序員。

一個簡單的原則,如果代碼中含有 ?var? 類型的變量,這段代碼就是傳統(tǒng)的指令式編程,如果代碼只有 ?val? 變量,這段代碼就很有可能是函數(shù)式代碼,因此學會函數(shù)式編程關鍵是不使用 ?vars? 來編寫代碼。

來看一個簡單的例子:

def printArgs ( args: Array[String]) : Unit ={
    var i=0
    while (i < args.length) {
      println (args(i))
      i+=1
    }
}

來自 Java 背景的程序員開始寫 Scala 代碼很有可能寫成上面的實現(xiàn)。我們試著去除 ?vars? 變量,可以寫成跟符合函數(shù)式編程的代碼:

def printArgs ( args: Array[String]) : Unit ={
    for( arg <- args)
      println(arg)
}

或者再簡化為:

def printArgs ( args: Array[String]) : Unit ={
    args.foreach(println)
}

這個例子也說明了盡量少用 ?vars? 的好處,代碼更簡潔明了,從而也可以減少錯誤的發(fā)生。因此 Scala 編程的一個基本原則上,能不用 ?Vars?,盡量不用 ?vars?,能不用 ?mutable?變量,盡量不用 ?mutable?變量,能有效避免函數(shù)的副作用,盡量不產(chǎn)生副作用。

第十二步: 讀取文件

使用腳本實現(xiàn)某個任務,通常需要讀取文件,本節(jié)介紹 Scala 讀寫文件的基本方法。比如下面的例子讀取文件的每行,把該行字符長度添加到行首:

import scala.io.Source
if (args.length >0 ){
  for( line <- Source.fromFile(args(0)).getLines())
    println(line.length + " " + line)
}
   else
      Console.err.println("Please enter filename")

可以看到 Scala 引入包的方式和 Java 類似,也是通過 ?import? 語句。文件相關的類定義在 scala.io 包中。 如果需要引入多個類,Scala 使用 _? 而非 ?*?。

通過前面兩篇文章的介紹,你應該對 Scala 編程有了一個大概的了解,可以編寫一些簡單的 Scala 腳本語言。Scala 的功能遠遠不止這些,在后面的文章我們在一一詳細介紹。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號