Scala學習筆記:重要語法特性

1.變量聲明


Scala 有兩種變量, val 和 var  val的值聲明後不可變,var可變前端

val msg: String = "Hello yet again, world!"java

或者類型推斷python

val msg = "Hello, world!"spring

2.函數定義


若是函數僅由一個句子組成,你能夠可選地不寫大括號。
編程

def max2(x: Int, y: Int) = if (x > y) x else y數組

3.for循環


打印每個命令行參數的方法是:
多線程

args.foreach(arg => println(arg))   
閉包

若是函數文本由帶一個參數的一句話組成,
app

args.foreach(println)less

Scala 裏只有一個指令式 for的函數式近似。

for (arg <- args)
   println(arg)

<- 的左邊是變量,右邊是數組。

再好比帶類型的參數化數組

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

    注意這裏的數組定義,只要new的時候帶類型Array[String]就好了,val後面自動推斷類型。
注意這裏的數組索引用的是()而不是java裏面的[]。
由於scala裏面根本沒有傳統意義上的操做符,取而代之的是他們均可以轉換爲方法。例如greetStrings(i)能夠轉換成 greetStrings.apply(i),
greetStrings(0) = "Hello"  將被轉化爲 greetStrings.update(0, "Hello")

儘管實例化以後你沒法改變 Array 的長度,它的元素值倒是可變的。所以,Array 是可變的對象。

4.List對象


建立一個 List 很簡單。 List裏面元素不可變。

val oneTwoThree = List(1, 2, 3)

List有個叫「 :::」的方法實現疊加功能。

val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour             

//結果是List(1, 2, 3, 4)

Cons 把一個新元素組合到已有 List的最前端,而後返回結果 List。 例如,若執行這個腳本:

val twoThree = list(2, 3)
val oneTwoThree = 1 :: twoThree
println(oneTwoThree)
//你會看到: List(1, 2, 3)


一個簡單的需記住的規則:若是一個方法被用做操做符標註,如 a* b,那麼方法被左操做數調用,就像 a.*(b)——除非方法名以冒號結尾。這種狀況下,方法被右操做數調用。所以, 1 :: twoThree 裏, ::方法被 twoThree 調用,傳入 1,像這樣: twoThree.::(1)。
類 List 沒有提供 append 操做,由於隨着列表變長 append 的耗時將呈線性增加,而使用::作前綴則僅花費常量時間。若是你想經過添加元素來構造列表,你的選擇是把它們前綴進去,當你完成以後再調用 reverse;

5.元組


與列表同樣,元組也是不可變的,但與列表不一樣,元組能夠包含不一樣類型的元素。

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

注意這裏第一個元素是從_1開始而不像List那樣從0開始。

6.Set和Map


var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"              

默認set或者HashSet可變。即jetSet = jetSet + "Lear"

若是要用不可變的set或者HashSet,要import

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")             

//這裏就不能再賦值給hashSet了

7.訪問級別

Public 是 Scala 的缺省訪問級別。C++中struct默認是public,class默認是private。java類中的變量默認是default類型,只容許在同一個包內訪問,通常用的時候跟private差很少。因此java用的時候要想用public必需要指出。

class ChecksumAccumulator {
private var sum = 0
...

}

8.靜態對象:object


Scala 比 Java 更面向對象的一個方面是 Scala 沒有靜態成員。替代品是, Scala 有單例對象: singleton object。除了用 object 關鍵字替換了 class 關鍵字之外,單例對象的定義看上去就像是類定義。

import scala.collection.mutable.Map
object ChecksumAccumulator {
private val cache = Map[String, Int]()
def calculate(s: String): Int =
if (cache.contains(s))
cache(s)
else {
.....
}

能夠以下方式調用 ChecksumAccumulator單例對象的calculate方法:

ChecksumAccumulator.calculate("Every value is an object.")

也不用在實例化了。
類和單例對象間的一個差異是,單例對象不帶參數,而類能夠。由於你不能用new關鍵字實例化一個單例對象, 你沒機會傳遞給它參數。

9.伴生對象


當單例對象與某個類共享同一個名稱時,他被稱做是這個類的伴生對象: companion object。你必須在同一個源文件裏定義類和它的伴生對象。類被稱爲是這個單例對象的伴生類: companion class。類和它的伴生對象能夠互相訪問其私有成員。
不與伴生類共享名稱的單例對象被稱爲孤立對象: standalone object。 因爲不少種緣由你會用到它

10.Main函數


要執行Scala程序,你必定要提供一個有main方法(僅帶一個參數, Array[String],且結果類型爲 Unit的孤立單例對象名。好比下面這個例子;

import ChecksumAccumulator.calculate 
object Summer {
     def main(args: Array[String]) {     //對比java裏面的public static void

    main(String[] args){}
         for (arg <- args)
         println(arg + ": " + calculate(arg))
     }
}

11.另外一種Main函數


Application 特質:在單例對象名後面寫上「 extends Application」 。而後取代main 方法.

import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends Application {
   for (season <- List("fall", "winter", "spring"))
   println(season +": "+ calculate(season))
}

效果和main函數同樣。不過它也有些缺點。首先, 若是想訪問命令行參數的話就不能用它,由於args數組不可訪問。第二,若是你的程序是多線程的就須要顯式的 main 方法。最後,某些JVM的實現沒有優化被 Application 特質執行的對象的初始化代碼。所以只有當你的程序相對簡單和單線程狀況下你才能夠繼承 Application 特質。

12.帶參數類聲明

Java 類具備能夠帶參數的構造器,而 Scala 類能夠直接帶參數。 Scala 的寫法更簡潔——類參數能夠直接在類的主體中使用;不必定義字段而後寫賦值函數把構造器的參數複製到字段裏。(不須要構造函數)
例如如下分數構造器:

class Rational(n: Int, d: Int) {
   require(d != 0) 
   override def toString = n +"/"+ d

  val numer: Int = n
   val denom: Int = d
  
   def add(that: Rational): Rational =      
   new Rational(
   numer * that.denom + that.numer * denom,
   denom * that.denom
}

require方法帶一個布爾型參數。若是傳入的值爲真,require將正常返回。反之,require將經過拋出IllegalArgumentException來阻止對象被構造。 這裏使用了重載。重載了類自帶的toString函數。 這裏that也就是隨便起了個名字的變量,不是關鍵字。this是關鍵字。好比下面這個函數:

def lessThan(that: Rational) =
this.numer * that.denom < that.numer * this.denom

這裏的this能夠省略。但下面這個就不能省略了:

def max(that: Rational) =
  if (this.lessThan(that)) that else this

13.if返回值


Scala 的 if 能夠產生值(是能返回值的表達式)。因而 Scala 持續了這種趨勢讓 for, try 和 match 也產生值。while不產生值,因此用得少。若是實在想用while,在純函數式編程的時候能夠考慮遞歸。
指令式風格:

var filename = "default.txt"
if (!args.isEmpty)
filename = args(0)

函數式風格:

val filename =
if (!args.isEmpty) args(0)
else "default.txt"

使用 val 而不是 var 的第二點好處是他能更好地支持等效推論。不管什麼時候均可以用表達式替代變量名。
如要替代 println(filename),你能夠這麼寫:

println(if (!args.isEmpty) args(0) else "default.txt")

14.break 和 continue


scala裏面也沒有break以及continue。若是想要用到他們的功能,可使用增長布爾變量控制到循環語句判斷中,相似:

var foundIt = false  while (i < args.length && !foundIt) { }

15.for過濾器


在for裏面還支持過濾器:

val filesHere = (new java.io.File(".")).listFiles    //路徑名的目錄中的文件的數組。
for (file <- filesHere if file.getName.endsWith(".scala"))
println(file)

甚至多個過濾器:

for (
  file <- filesHere
  if file.isFile;
  if file.getName.endsWith(".scala")
) println(file)


16.for循環生成新集合


for {子句} yield {循環體} 製造新集合
例如:

def scalaFiles =
for {
file <- filesHere
if file.getName.endsWith(".scala")
} yield file

這樣每一步就不是打印一個file,而是將file存儲起來,最終產生一個Array[File]

17.try catch  finally

try {
     val f = new FileReader("input.txt")
     openFile(file)

} catch {
   case ex: FileNotFoundException =>  new FileReader("input.txt")
   //注意這裏依然有返回值。使用=>符號
   case ex: IOException => // Handle other I/O error
}
finally {
   file.close() // 確保關閉文件。
}

這裏catch {
   case ex: 。。。
   case ex: 。。。
}

對比java,catch是這樣用的 
catch (Exception e) { 。。。。} 並且常常在catch裏面throw。scala通常不使用throw。

還有,finally裏最好只作一些關閉或打印之類的操做,不要有反作用的表達式,這樣會有無謂的返值。

18.switch語句


match語句就像java裏的switch語句。

val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
case "salt" => println("pepper")
case "chips" => println("salsa")
case "eggs" => println("bacon")
case _ => println("huh?")
}

差異:
一、Java 的 case 語句裏面的整數類型和枚舉常量。而這裏能夠是任意類型,甚至正則匹配
二、在每一個可選項的最後並無 break。取而代之, break是隱含的。
三、match 表達式也能產生值:例如能夠這樣:

val friend =
firstArg match {
  case "salt" => "pepper"
  case "chips" => "salsa"
  case _ => "huh?"
}
println(friend)

19.函數嵌套定義


函數:函數式編程風格的一個重要設計原則:程序應該被解構成若干小的函數, 每一個完成一個定義良好的任務。在java中一般這樣作:

def processFile(filename: String, width: Int) {
   ...
  for (line <- source.getLines)
   processLine(filename, width, line)
}
private def processLine(filename:String, width:Int, line:String) {
  ....
}

Scala 提供了另外一種方式:你能夠把函數定義在另外一個函數中。就好象本地變量那樣,這種本地函數僅在包含它的代碼塊中可見。

def processFile(filename: String, width: Int) {
def processLine(filename:String, width:Int, line:String) {
  ...
}
val source = Source.fromFile(filename)
for (line <- source.getLines) {
processLine(filename, width, line)
}
}

20.Lambda函數


Scala 擁有第一類函數: first-class function。你不只能夠定義函數和調用它們,還能夠把函數寫
成沒有名字的文本: (跟python裏面的lambda函數差很少)

scala> var increase = (x: Int) => x + 1
scala> increase(10)
res0: Int = 11

若是你想在函數文本中包括超過一個語句,用大括號包住函數體,當函數值被調用時,全部的語句將被執行,而函數的返回值就是最後一行產生的那個表達式。

scala> increase = (x: Int) => {
  println("We")
  println("are")
  println("here!")
  x + 1
}

21.集合通配符:_


Scala 提供了許多方法去除冗餘信息並把函數文本寫得更簡短。好比去除參數類型以及被推斷的參數以外的括號:

scala> someNumbers.filter(x => x > 0)

若是想讓函數文本更簡潔,能夠把下劃線當作一個或更多參數的佔位符,只要每一個參數在函數文
本內僅出現一次。 :

scala> someNumbers.filter(_ > 0)

還可使用一個下劃線替換整個參數列表。叫偏應用函數

scala> def sum(a: Int, b: Int, c: Int) = a + b + c

通常調用能夠這樣:

scala> sum(1, 2, 3)

用偏函數取而代之:

scala> val a = sum _  //請記住要在函數名和下劃線之間留一個空格
scala> a(1, 2, 3)

再好比

someNumbers.foreach(println _)

22.閉包


閉包:是指能夠包含自由(未綁定到特定對象)變量的代碼塊
任何帶有自由變量的函數文本,如(x: Int) => x + more,都是開放術語:因爲函數值是關閉這個開放術語(x: Int) => x + more 的行動的最終產物, 獲得的函數值將包含一個指向捕獲的 more 變量的參考, 所以被稱爲閉包。

scala> var more = 1
scala> val addMore = (x: Int) => x + more
scala> addMore(10)
res19: Int = 11

每次函數被調用時都會建立一個新閉包。每一個閉包都會訪問閉包建立時活躍的 more 變量。

23.重複參數


想要標註一個重複參數,在參數的類型以後放一個星號。例如:

scala> def echo(args: String*) =
        for (arg <- args) println(arg)

這樣定義, echo 能夠被零個至多個 String 參數調用:

scala> echo()
scala> echo("one")
scala> echo("hello", "world!")

可是若是有一個數組變量
scala> val arr = Array("What's", "up", "doc?"),則不能像scala> echo(arr)這樣調用。
你須要在數組參數後添加一個冒號和一個_*符號,像這樣:

scala> echo(arr: _*)

24.curry化


Scala 容許你建立新的「感受像是原生語言支持」的控制抽象:scala提供curry 化:
Scala裏的Curry化能夠把函數從接收多個參數轉換成多個參數列表。若是要用一樣的一組實參屢次調用一個函數,能夠用curry化來減小噪音,讓代碼更有味道。咱們要編寫的方法不是接收一個參數列表,裏面有多個參數,而是有多個參數列表,每一個裏面能夠有一個或多個參數。也就是說,寫的不是def foo(a: Int, b: Int, c: Int){},而是 def foo(a: Int)(b: Int)(c: Int){}。能夠這樣調用這個方法,好比:foo(1)(2)(3)、foo(1){2}{3},甚至這樣foo{1}{2}{3}。

例如,傳統函數以下:

scala> def plainOldSum(x: Int, y: Int) = x + y

scala> plainOldSum(1, 2)     //res5: Int = 3

scala容許你使用curry化的新型函數:

scala> def curriedSum(x: Int)(y: Int) = x + y
scala> curriedSum(1)(2)             //res5: Int = 3

結果同樣。

25.帶函數參數的函數


高階函數: higher-order function——帶其它函數作參數的函數

def filesMatching(query: String,
  matcher: (String, String) => Boolean) = {
  for (file <- filesHere; if matcher(file.getName, query))
    yield file
}

這裏matcher實際上是一個函數,這裏作了filesMatching函數的參數。
(String, String) => Boolean)表示matcher函數的參數是(String, String)類型,而返回值是Boolean 類型
你能夠經過讓多個搜索方法調用它,並傳入合適的函數:

def filesEnding(query: String) =
  filesMatching(query, _.endsWith(_))

這就至關於

def filesEnding(query: String) =
  for (file <- filesHere; if file.getName.endsWith(query))
   yield file

由於上面matcher: (String, String)裏面兩個參數, matcher(file.getName, query)
因此 _.endsWith(_)裏面的第一個_對應於字符串file.getName,第二個_對應於字符串query,
連在一塊兒就是file.getName.endsWith(query)

相似的

def filesContaining(query: String) =
  filesMatching(query, _.contains(_))

就至關於

def filesContaining(query: String) =
   for (file <- filesHere; if file.getName.contains(query))
    yield file


26.傳名參數


Scala中容許無參數的函數做爲另外一函數的參數傳遞進去,也就是傳名參數(call-by-name)
定義一個函數 myAssert ,而其參數則爲傳名參數。在這裏,咱們想要實現的是斷言,能夠將傳名參數寫成函數文本的格式:(…) => Type ,即參數列表 => 類型。

def myAssert(check: () => Boolean) =
       if(!check()){
              println("OK ...")
              throw new AssertionError
       }

上面的函數定義了一個當客戶代碼傳入的函數值(這裏咱們用()指明,表明省略了該函數的參數列表。調用方式以下:

scala> myAssert(() => 5 < 3)

客戶端代碼中的 () => 5 < 3 彷佛有點繁瑣,若是可以直接傳入 5 < 3 之類的布爾表達式就更好了。這是能夠實現的。只須要將函數定義 def 中空參數列表即小括號對()去掉,直接用 => 而不是()=> 就能夠了。此外,if 判斷中的 check 後面的()也要同時去掉。修改後的代碼以下:

def myAssert(check: => Boolean) =       if(!check){              println("OK ...")              throw new AssertionError       } myAssert(5 < 3)

相關文章
相關標籤/搜索