《Scala 程序設計》學習筆記 Chapter 3:要點詳解

操作符重載?

  • 舉個例子[P60]
    • 1 + 2 中的 +操作符是一個方法
  • 首先,在 Scala 中,萬物皆對象,包括基本數據類型。其次,使用中綴表達式法表示單參數方法時,其中的點號和括號可以省略,所以:1 + 2 等價于 1.+(2) 。[P60]
  • 調用無參方法也可以省略點號,這種寫法也被稱為后綴表示法。由于有的時候后綴表示法會觸發歧義,所以 Scala 2.10+ 將其作為可選特性,需要 import scala.language.postfixOps ,或者使用 scala -language:postfixOps 開啟 REPL 來開啟這個特性。[P60 - 61]
  • 使用 scala -feature 開啟 REPL 可以獲取更多有意義的警告信息。[P61]
  • 允許出現在標示符中的字符:[P61 - 62]
    [略]
    • Scala 沒有自增 / 自減運算符。
    • 在模式匹配表達式中,以小寫字母開頭的標記會被解析為變量標示符,而大寫字母開頭的標記則會被解析為常量標示符(如類名)。

無參數方法

  • Scala 允許用戶決定是否為無參數方法使用括號:[P63]

    • 在定義無參數方法時如果省略括號,那么調用該方法的時候必須省略括號。但是如果定義時沒有省略括號,用戶在調用時可以選擇省略或不省略。
  • Scala 社區的習慣:[P63]

    • 定義無副作用的無參方法時省略括號,有副作用的則添加括號。
  • scala 或者 scalac 中添加 -Xlint 參數時,定義有副作用的無參方法,省略括號時系統會發出警告。

  • 一個經典的省略括號的例子(方法鏈):[P63 - 64]

    def isEven(n: Int) = (n % 2) == 0
    List(1, 2, 3, 4) filter isEven foreach println
    // 展開(提示:關注一下每一個方法需要的參數,這里邊并沒有省略任何變量參數)
    
    List(1, 2, 3, 4).filter((i: Int) => isEven(i)).foreach((i: Int) => println(i))
    List(1, 2, 3, 4).filter(i => isEven(i)).foreach((i: Int) => println(i))
    List(1, 2, 3, 4).filter(isEven).foreach(println)
    List(1, 2, 3, 4) filter isEven foreach println
    

    簡單梳理一下:filter 接收一個函數參數 isEven ,并返回一個新的 List ,然后調用 foreach ,它也接收一個函數參數,這個函數正好是 println 。其實參數一點都沒省略,就是單純的省略括號和省略 . 運算符。

    假如方法鏈中的某一個方法接收 0 個或大于 1 個的參數,需要為部分或全部方法補上點號。

優先級規則

  • 優先級表 [P64]

    1. 所有字母
    2. |
    3. ^
    4. &
    5. < >
    6. = !
    7. :
      • / %
    8. 其他特殊字符
  • 當 = 用于賦值時,優先級最低。[P64]

  • 在 Scala 中,任何名字以 : 結尾的方法都與右邊的對象所綁定,其他的方法則是左綁定的。[P65]

  • 舉個例子:cons 操作:使用 :: 方法將某一元素放置到列表前邊。[P65]

    val list = List('b', 'c', 'd')
    val nList = 'a'::list
    

領域特定語言

[P65 - 66][略]

Scala 的 if 語句

  • Scala 的 if 語句是具有返回值的表達式。[P66]

  • if 語句的返回值類型也被稱為所有條件分支的最小上界,也就是與每條 each 子句可能返回值類型最接近的父類型。以下例子中返回 String :[P66]

    val configFile = new java.io.File("somefile.txt")
    val configFilePath = if (configFile.exists()) {
        configFile.getAbsolutePath() // String
    } else {
        configFile.createNewFile()
        configFile.getAbsolutePath() // String
    }
    
  • Scala 沒有三元表達式。[P66]

Scala 中的 for 推導式

生成器表達式

  • <- [?P67]

保護式:篩選元素

  • 通過加入 if 表達式來篩選元素,這些表達式也被稱為保護式。[P68]

    val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier")
    for (breed <- dogBreeds
        if breed.contains("Terrier")
    ) println(breed)
    
    val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier")
    for (breed <- dogBreeds
        if breed.contains("Terrier")
        if !breed.startsWith("Yorkshire")
    ) println(breed)
    // 等價于
    for (breed <- dogBreeds
        if breed.contains("Terrier") && !breed.startsWith("Yorkshire")
    ) println(breed)
    

Yielding

  • 使用 yield 關鍵字在 for 表達式中生成新的集合,使用大括號替代圓括號,以相似的方法把參數列表封裝在大括號中:[P68]

    val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier")
    val filteredDogBreeds = for {
        breed <- dogBreeds
        if breed.contains("Terrier) && !breed.startsWith("Yorkshire")
    } yield breed
    
  • for 推導式僅包含單一表達式時使用圓括號,當其包含多個表達式時使用大括號。[P69]

擴展作用域與值定義

  • Scala 允許在 for 表達式中的最初部分定義值,并可以在后面的表達式中使用該值。[P69]

    val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier")
    for {
        breed <- dogBreeds
        upcasedBreed = breed.toUpperCase() // 盡管 upcasedBreed 不可變,但并不需要使用 val 對其進行限定。
    } println(upcasedBreed)
    
  • for 表達式中使用 Option :[P69 - 70]

    val dogBreeds = List(Some("Doberman"), None, Some("Yorkshire Terrier"), Some("Dachshund"))
    for {
        breedOption <- dogBreeds
        // 系統隱式的加上了 if breedOption != None
        breed <- dogBreeds
        upcaseBreed = breed.toUpperCase()
    } println(uppercaseBreed)
    
    for {
        SOme(breed) <- dogBreeds
        upcasedBreed = breed.toUpperCase()
    } println(upcasedBreed)
    
  • 當遍歷某一集合或其他像 Option 這樣的容器并試圖提取值時,應該使用箭頭。當執行并不需要迭代的賦值操作時,應該使用等于號。[P70]

使用 trycatchfinal 語句

  • Scala 不支持已被視為失敗設計的檢查型異常( checked exception )。Scala 將 Java 中檢查異型異常視為非檢查型,而且方法聲明中也不包含 throw 子句。[P72]

  • Scala 提供了有助于 Java 互操作的 @throws 注解。[P72]??

  • 在 Scala 中,異常處理用模式匹配來處理:[P72 - 73]

    var source: Option[Source] = None
    try {
        source = Some(Source.fromFile(fileName))
        val size = source.get.getLines.size
        println(s"file $fileName has $size lines")
    } catch {
        case NonFatal(ex) => println(s"Non fatal exception! $ex)" // 捕獲 *非致命* 錯誤。
    } finally {
        for (s <- source) {
            println(s"Closing $fileName...")
            s.close
        }
    }
    
  • 在 SBT 中,使用 run-main 啟動程序。啟動程序前允許傳遞參數。[P74]

  • Scala 允許自定義異常:throw new MyBadException(...) 。如果自定義異常是一個 case 類,那么拋出異常時可以省略 new 關鍵字。[P74]

  • 自動資源管理:ScalaARM

名字調用和值調用 [P75 - 78]

  • manage.apply 方法

    def apply[
        R <: { def close():Unit }, // 1
        T]
        (resource: => R) // 2
        (f: R => T) = {...} // 3
    
    1. <: 表明 R 屬于其他類的子類。 在本例中,R 的父類型是一個包含 close():Unit 方法的結構類型。
      • 結構化類型允許我們使用反射機制嵌入包含 close():Unit 方法的任意類型,但會造成許多系統開銷,結構化類型也的代價也十分昂貴,所以 Scala 將反射列為可選特性。
    2. 傳名參數:在進入 manage.apply 之前,該表達式都不會被執行,直到在 apply 中調用這個參數。(延遲計算)
    3. 匿名函數,輸入為 resource 返回值類型為 T
  • 傳名參數的行為與函數類似,每次使用該參數時便會執行表達式。[P77]

  • 運用傳名參數,以遞歸取代循環結構:[P77]

    @annotation.tailrec
    def continue(conditional: => Boolean)(body: => Unit) {
        if (conditional) {
            body
            continue(conditional)(body)
        }
    }
    
    var count = 0
    continue(count < 5) {
        println(s"at $count")
        count += 1
    }
    
  • 傳名參數會在每次被引用時估值。它的求值會被推遲,并可能一再地被重復調用,因此此類參數具有惰性。除此之外,Scala 還提供了惰性值。[P78]

惰性賦值

  • 以延遲的方式初始化某個值,并且表達式不會被重復計算。常見場景:[P78]

    • 表達式執行代價昂貴。
    • 縮短模塊的啟動時間,將不需要的工作推遲。
    • 確保對象中的其他字段的初始化過程優先執行。
  • 舉個例子:[P78]

    object ExpensiveResource {
        lazy val resource: Int = init() // 推遲到需要時計算。
        def init(): Int = {
            // Some Expensive Operations.
            0
        }
    {
    
  • Scala 通過保護式( guard )來實現惰性值,由于無法解除保護式,所以會造成額外的開銷。只有當保護式帶來的額外開銷小于初始化帶來的開銷,或者某些值惰性化能簡化系統初始化過程,并確保執行順序滿足依賴條件時才應該使用惰性值。[P78]

枚舉

  • Scala 定義了 Enumration 類來支持枚舉,意味著 Scala 不為枚舉提供特殊語法。[P79]

  • 一個例子:[P79 - 80]

    object Breed extends Enumeration {
        type Breed = Value
        val doberman = Value("Doberman Pinscher")
        val yorkie = Value("Yorkshire Terrier")
    }
    
    // 在其他類中
    for(breed <- Breed.values) println(s"${breed.id}, $breed")
    
    • 使用 .values 取得枚舉列表。

    • Value 對象會自動從 0 開始逐一遞增分配 ID 。

    • 在上述定義中,BreedValue 類型的一個別名,目的在于:

      def isTerrier(b: Breed) = b.toString.endsWith("Terrier")
      Breed.values filter isTerrier foreach println
      
    • 如果沒有類型別名,上邊的代碼會出現語法錯誤。

  • 有時不希望給枚舉值命名:[P80]

    object WeekDay extends Enumeration {
        type WeekDay = Value
        val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
    }
    
    def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
    WeekDay.values filter isWorkingDay foreach println
    
  • case 類相比枚舉值有兩大有點:允許添加方法和字段;適用于包含未知枚舉值的場景。[P81]

可插入字符串

  • 在使用 printf 格式字符串時(不是 printf 函數),Scala 會調用 Java 的 Formatter 類。printf 格式指令與 ${...} 之間不能有空格。[P81]
  • 在格式字符串中,將 DoubleInt 格式化輸出會引發編譯錯誤。[P82]
  • Scala 編譯器會在某些語境中對 Java 字符串進行封裝并提供一些額外的方法,這些定義在 scala.collection.immutable.StringLike 中。[P82]
  • “原生”( raw )插入器不會對控制字符串進行擴展。[P82]
  • Scala 允許自定義字符串插入器。[P83]

Trait :Scala 語言的接口和“混入”

  • Scala 使用 trait 來替代接口。(一種簡單的理解是:允許將聲明方法實現的接口)[P83]

  • trait 支持“混入”,即不只是定義方法和字段。可以參考書上關于日志系統的代碼。[P84]

  • 使用 with 關鍵字混入 trait 。Scala 允許混入多個 trait ,混入的 trait 中的方法彼此獨立。[P84 - 85]

  • 子類向父類傳參的方式:

    class LoggedServiceImportante(name: String) extends ServiceImportante(name) with StdoutLogging {...} // 子類將 name 傳遞給父類
    
  • Scala 要求所有方法覆寫都要使用 override 關鍵字。與 Java 一樣,Scala 使用 super.methodName 訪問父類方法。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容