《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阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容