scala筆記(二)

1、函數和閉包
1.本地函數
概念上來講就是函數中的函數,相似於本地變量。訪問的範圍也一樣侷限於定義的函數中
2.函數字面量
簡單來講就是能夠把函數當字面量同樣的定義和使用,例如能夠定義(x:Int) => x + 1這樣的匿名函數,也能夠把它賦給某個變量 val increase = (x:Int) => x + 1,而後使用increase(10)這種方式進行調用。當定義的函數有多條語句時可使用花括號,例如:java

(x:Int) => 
{
 println("Hello")
 x + 1
}

當函數字面量被看成參數傳入別的函數中時,某些時候可使用一些簡寫的方式,例如:數組

val numbers = List(1,2,3,4,5,6)
    /**下面的函數字面量中x的類型能夠被推斷出來,因此類型聲明能夠省略,x外的括號也能夠被省略,寫成 numbers.filter(x => x > 3)*/
   numbers.filter((x) => x > 3)

上述的函數字面量還可使用佔位符方式進行更簡寫的方式進行簡化,當函數字面量中的參數只在字面量函數中出現一次,便可使用下劃線代替這個參數,即上面的方式還能夠簡化爲:閉包

numbers.filter(_ > 3)

當你自定義函數字面量而沒有做爲參數傳入某個函數時,scala的類型推斷是不起做用的,例如:val test = _+_,這樣的定義會報錯,能夠在下劃線後加入類型聲明,val test = _:Int+_:Int,此時的兩個下劃線表明兩個參數,而不是同一個。架構

3.部分應用函數
下劃線不只能夠只替代一個參數,還能夠替代整個的參數列表,例如定義以下的函數app

def sum(a:Int,b:Int,c:Int) = a + b + c
 val a = sum _
 a(1,2,3)

以上的程序中,sum _是一個部分應用函數,下劃線表明着多個參數,本例中是三個。而後賦值給a,而後傳給a三個參數。在編譯時scala會生成一個a的單例對象,對象中有apply方法包含三個參數。至關於調用,a.apply(1,2,3)還能夠把sum包裝成另一種只須要一個,或者兩個參數的函數,好比ide

val b = sum(1,_:Int,3)
b(2)//至關於b.apply(1,2,3)

4.閉包
其實這個概念我一直講不太清楚,仍是舉個書上的例子吧。好比定義一個函數字面量:val a = (x:Int) => x + other直接這麼定義的話確定會報錯,由於找不到other這個變量。但下面這種定義就能夠:函數

var other = 10
val a = (x:Int) => x + other

像上面這樣定義的在運行時建立的函數值,被稱爲閉包。緣由other在函數字面量定義時被稱做自由變量,而在運行時根據函數字面量建立函數值時須要捕獲這個自由變量才能完成函數值的建立。在捕獲自由變量前的函數字面量被稱爲開放項,而捕獲自由變量建立函數值的這個過程便是關閉這個開放項,因此被稱爲閉包。而捕獲的這個自由變量是捕獲自由變量的自己,並非它指向的值。簡單來說就是即便閉包建立以後,other變量指向的值改變了,即other = 20,則a(10)的值也會變爲30。
外面的other的改變影響了閉包以內函數值的變化,一樣閉包內的變化也能夠影響外面的變量。好比:優化

val arr = Array(1,2,3,4,5)
var sum = 0
//處於外面的sum變量在sum閉包中被改變,也影響到了外面的sum變量
arr.foreach(sum += _)

若是閉包建立了多個備份,且被調用了屢次,則使用的實例是在閉包被建立時活躍的。例如:spa

def increaser(a:Int) = (x:Int) => a + x
val inc10 = increaser(10)
val inc20 = increaser(20)
//此時inc10上a綁定的值是10,inc20上面綁定的值是20
println(inc10(10))//20
println(inc20(20))//40

5.可變長度參數
函數的參數列表中有多個相同類型的參數時,可使用類型後加一個星號進行簡化,例如:
def test(args:String*){}
test("a","b","c")
/**在test內部args被轉化爲一個數組,但若是直接給test一個數組的話會報錯,須要傳數組的話,可使用下面這種方式傳*/
//_*這個符號告訴編譯器將arr的每一個元素看成參數傳遞
test(arr:_*)scala

6.尾遞歸
和普通的遞歸相比,尾遞歸的最後一次調用是遞歸函數自己。和尾遞歸相比遞歸每次都使用大量的棧空間來建立自身的調用,但尾遞歸能夠只調用函數自己。好比

//此示例不屬於尾遞歸,由於最後一次調用函數以後執行了別的操做
def boom(x:Int):Int={
  if(x == 0) throws new Exception("boom!")
  else boom(x - 1) + 1
}
//此次就是尾遞歸了,最後一次調用爲函數自己,且沒有多餘操做
def boom(x:Int):Int={
  if(x == 0) throws new Exception("boom!")
  else boom(x - 1)
}

但尾遞歸優化因爲scala運行在JVM上,致使侷限性很大。scala中的尾遞歸優化只能應用於每次遞歸調用都是調用函數自身的程序,例以下面兩個例子這樣也是沒法應用優化的。

//兩個函數間相互調用
   def isEven(x:Int):Boolean = if(x == 0) true else isOdd(x - 1)
   def isOdd(x:Int):Boolean = if(x == 0) false else isEven(x - 1)

  //調用函數值
  val funValue = nestedFun _
  def nestedFun(x:Int){
    if(x != 0){println(x);funValue(x - 1)}
 }

2、控制抽象
1.減小代碼重複
就是使用函數值這樣的高階函數來儘可能減小重複的代碼工做,舉個書中的栗子,要遍歷一個文件列表,從中找出符合條件的文件,以下:

object FileMatcher{
  private def filesHere = new java.io.File(".").listFiles
  private def fileMatching(matcher:(String,String) => Boolean)={
      for (file <- filesHere; if matcher(file.getName)) yield file
    }   
  def filesEnding(query:String)={
    fileMatching(_.endsWith(query))
  }
  def filesContaining(query:String)={
    fileMatching(_.contains(query))
  }
  def filesRegex(query:String)={
    fileMatching(_.matches(query))
  }
}

上面的單例對象對外提供了三個功能,分別能夠查找以某些字符結尾的,包含某些字符的和匹配正則的文件。函數中使用了傳遞函數值和閉包,若是使用Java的話,上面的工做量就會不小了。

2.柯里化
指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。例如:

def curriedSum(x:Int)(y:Int) = x + y
  //調用
  curriedSum(1)(2)
  //實際的調用過程是調用了兩個普通函數,以下
  def first = (x:Int) => x + y
  def second = first(1)
  second(2)

*有一個小技巧的地方是,當函數只有一個參數的時候,調用函數的時候小括號()能夠替換爲花括號{}

3.傳名參數
與函數值不一樣,傳名參數沒有參數的只有表達式。例如一個斷言架構,設置一個標誌位,當標誌位爲假時,什麼都不作,爲真時則返回表達式的值:

var flag = true
//傳名參數的定義,不包含參數,使用=>開頭後加返回類型
def byNameAssert(predicate: => Boolean)={
  if(flag) predicate
}
println(byNameAssert(1 > 2))
flag = false
println(byNameAssert(1 > 2))

3、組合與繼承
1.抽象類
定義的語法同Java同樣,使用abstract開頭。惟一不一樣的地方是抽象類中的抽象方法不須要使用abstract開頭來定義,和普通方法定義一致,但不須要寫方法體。 例如 def test(a:Int):Int
2.定義無參方法
定義:def test:Int = 10
使用慣例,只要方法中沒有參數且方法僅可以經過所包含對象的屬性去訪問可變狀態(方法不能改變可變狀態),就使用無參方法。使用另外一種描述是若是函數執行了操做即便沒有參數也應該帶一個空括號,若是函數僅僅是讀取了某個屬性值,則能夠省略空括號。爲的是客戶端代碼調用時的功能明確性。
無參方法和字段的區別,一是訪問速度方面,字段更快一些,由於字段在類初始化時就計算好了值,而方法則是在每次調用時進行計算。另外一方面是字段會佔用比方法更多的內存空間
3.重寫方法和字段
子類能夠重寫父類的無參方法或非私有字段。
Java中有四個命名空間字段、方法、類型和包,scala中只有兩個值(字段、方法、包和單例對象)和類型(類和物質名)。因此在scala中不容許同一個類中出現同名的字段和方法
scala中重寫父類方法須要override關鍵字,在實現抽象方法時則不須要。
4.調用超類構造器
這個比較簡單,就舉一個例子

class A(a:Int){}
   class B(b:Int,c:Int) extends A(a)

4、層級
1.scala的類層級
scala中全部的類都是Any的子類。繼承關係以下圖。Any中默認定義了一些經常使用的方法,好比==、!=、equals、hashCode、toString。其中==和!=被定義爲final不能被子類重寫
圖片描述

Any有兩個子類,一個是AnyVal另外一個是AnyRef。AnyVal是scala內建值的基類。除8個基本類型(與Java中的基本類型對應)外,Unit相似於Java中的void,表示沒有任何返回值,Unit只有一個實例值是()。一些基本類型的的操做,好比 1 to 5,像這種基本類中沒有的方法就會經過隱式轉換,將Int轉換爲了RichInt類型。
另外一個AnyRef是全部引用類型的基類,相似於Java中的Object。在scala中的類還繼承自一個名爲ScalaObject的特質,但願經過其中包含的編譯器定義和實現加速scala程序。目前物質中只有一個方法$tag,在內部使用加速模式匹配。
2.底層類型
scala類層級中有兩個底層類型Null和Nothing。Null是全部引用類型的子類,不兼容值類型。Nothing則是全部類型的子類型,但並無任何值,它的做用之一是標明不正常終止,例如scala標準庫中的Perdef對象有一個方法,定義以下:

def error(message:String):Nothing={throw new RuntimeException(message)}
//由於error的返回類型爲Nothing,這個方法能夠在任何返回類型的方法中調用
def test(a:Int,b:Int):Int={if (a > b) a else error("exception")}
相關文章
相關標籤/搜索