美文网首页
《Scala 程序设计》学习笔记 Chapter 2:更简洁 更

《Scala 程序设计》学习笔记 Chapter 2:更简洁 更

作者: 云之外 | 来源:发表于2017-03-20 23:02 被阅读180次

    分号

    • 在 scala REPL 中,使用 :paste 模式输入多行代码,然后用 Ctrl-D 结束。[P29]

    变量声明

    • valvar 关键字只标识引用本身是否可以指向另一个不同的对象,它们并未表明其所引用的对象是否可变。[P30]
    • 为了减少可变性引起的 bug ,应当*尽可能地使用不可变变量。[p30]

    Range [P31]

    1. 1 to 10 : 1, 2, .., 10
    2. 1 until 10 : 1, 2, 3, ..., 9
    3. 1 to 10 by 3 : 1, 4, 7, 10
    4. 1 to 10 by -3 : 10, 7, 4, 1
    5. 1L to 10L by 3L
    6. 1f to 10.3f by 0.3f
    7. 'a' to 'g' by 3

    偏函数

    • ”偏“:偏函数不处理所有可能的输入,只处理能与至少一个 case 语句匹配的输入。[P32]

    • 在偏函数中只能用 case 语句,而整个函数必须用花括号包围。[P32]

    • 如果偏函数被调用,而函数的输入却与所有语句都不匹配,系统会抛出一个 MatchError 运行时错误。[P32]

    • 使用 isDefinedAt 方法测试特定输入是否与偏函数匹配:f.isDefinedAt(x) 返回 true / false 。[P32]

    • 可以使用 orElse 语法连接偏函数:[P32]

      val pf1 = PartialFunction[Any, String] = {case s:String => "YES"}
      val pf2 = PartialFunction[Any, String] = {case d:Double => "YES"}
      val pf = pf1 orElse pf2
      

    方法声明

    • copy 方法也是 case 类自动创建的。它允许你在创建 case 类的新实例时,只给出与原始对象不同部分的参数。[P33]

    方法具有多个参数列表

    • Scala 允许我们把参数列表两边的圆括号替换为花括号。[P34]

      def draw(offset: Point = Point(0.0, 0.0))(f: String => Unit): Unit = f(s"draw(offset = $offset), ${this.toString}")
      
      s.draw(Point(1.0, 2.0))(str => println(s"ShapesDrawingActor: $str"))
      // 等价于
      s.draw(Point(1.0, 2.0)){
          str => println(s"ShapesDrawingActor: $str"))
      }
      
    • 使用具有多个参数列表的方法有助于 Scala 进行参数推断。[�P35]

    • 使用具有多个参数列表的方法中,可以使用最后一个参数列表推断隐含参数。�[P35]

    • 隐含参数是用 implicit 关键字声明的参数,当相应方法被调用时,我们可以显式指定这个参数,或者不指定,让编译器在当前作用域找到一个合适的值作为参数。[P35]

    • 隐含参数可以代替参数默认值,而且更加灵活。[P35]

    Future 简介

    • scala.concurrent.Future� 是 Scala 提供的一个并发工具,其 API 使用隐含参数减少代码冗余。[P35]

    • Future 与隐含参数[P36]

      apply[T](body: => T)(implicit executor: ExecutionContext): Future[T]
      
      import scala.concurrent.ExecutionContext.Implicits.global
          val future = {
              ...
          }
      
    • 只有用 implicit 关键字声明,在当前作用域可见的对象才能用作隐含值;只有被声明为 implicit 的函数参数才允许调用时不给出实参,而采用隐含的值。��[P37]

    嵌套方法的定义与递归

    • 内部变量会屏蔽外部相同名称的变量。[P38]

    • Scala 采用局部作用域类型推断,所以必须为递归方法显式声明返回值类型。[P39]

    • 尾递归:调用递归函数是该函数的最后一个表达式,该表达式的返回值就是所调用的递归函数的返回值。[P39]

    • 尾递归检查 @tailrec [P39]

      import scala.annotation.tailrec
      
      def factorial(i: Int): Long = {
          @tailrec
          def fact(i: Int): Long = {
              if (i <= 1) accumulator
              else fact(i - 1, i * accumulator)
          }
          fact(i, 1)
      }
      
      • 如果函数不是尾递归,编译器会报错。
    • 外层方法中的一切在嵌套方法中都是可见的,包括传递给外层方法的参数。

    推断类型信息

    • 什么时候需要显式类型注解:[P41]

      • 声明了可变的 var 变量或不可变的 val 变量,没有进行初始化。
      • 所有的方法参数(如 def deposit(amount: Money) = {...} )。
      • 方法的返回值类型,在以下情况中必须显式声明其类型。
        • 在方法中明显地使用了 return (即使在方法末尾也是如此)。
        • 递归方法。
        • 两个或多个方法重载(拥有相同的函数名),其中一个方法调用了另一个重载方法,调用者需要显式类型注解。
        • Scala 推断出的类型比你期望的类型更为宽泛,如 Any 。(少见)
    • Scala 支持可变方法,但是:[P42]

      • 方法可以用有其他参数,但必须位于可变参数之前;
      • 方法只能有一个可变参数。
    • 加入返回类型注解可以在编译阶段发现推断类型错误,也有助于提升代码可读性。[P43]

    • Scala 将方法体前的声明和等号当做函数定义,而在函数式编程中,函数总要有返回值。当 Scala 发现函数体之前没有等号时,就认定程序员希望该方法是一个 procedure ,意味着它只返回 Unit 。[P44]

    • Unit 类型拥有一个名为 () 的值。[P44]

    保留字 [P44 - 45]

    • forSome :用在已经存在的类型声明中,限制其能够使用的具体类型。

    • implicit :使得方法或变量值可以被�������用于隐含转换;将方法参数标记为可选的,只要在调用该方法时,作用域内有类型匹配的候选对象,就会使用该对象作为参数。

    • lazy :推迟 val 变量的赋值。

    • requires :[已停用]

    • sealed:用于父类型,要求所有派生的子类必须在同一个源文件中声明。

    • trait:这是一个混入模块,对类的实例添加额外的状态和行为;也可以用于声明而不实现方法,类似 Java 的 interface

    • type:声明类型。

    • yield:在 for 循环中返回元素,这些元素会构成一个序列。

    • 还有一些字符,具体看文档。

    • Scala 没有 breakcontinue

    • 在 Scala 中引用保留的 Java 方法时,要为其加上 ``

      java.util.Scanner.`match`
      

    字面量

    整数字面量

    • 整数字面量如果超过规定的范围,会引发一个编译错误。[P46]

    字符字面量

    • 不可打印的 Unicode 字符(如:\u0009 水平制表符)在 Scala 中是不允许的。[P48]

    字符串字面量

    • 字符串字面量是被双引号或者三重引号包围的字符串序列,如 """...""" 。[P48]
    • 用三重双引号包含的字符串字面量被称为多行字符串字面量,这些字符串可以跨越多行, 换行符是字符串的一部分。可以包含任意字符,但不能出现三个连续的双引号。三重双引号包含的字符串不转义。[P49]
    • 在多行字符串中,可以使用 String.stripMargin 移除每行字符串开头的空格和第一个遇到的垂直分割符 | 。 [P49]
      • 如果希望用别的字符替代 | ,可以使用 stripMargin 的重载版本,该函数可以指定一个 Char 参数替代 | 。如果想要移除整个字符串(而不是字符串的各个行)的前缀和后缀,有相应的 stripPrefixstripSuffix 方法可以完成。

    符号字面量

    • Scala 支持符号。符号是一些规定的字符串。两个同名符号会指向内存中的同一对象。[P50]
    • 符号是单引号( ' )后边跟上一个或多个数字、字幕或下划线,但第一个字符不能是数字。[P50]
    • 符号字面量 'id 是表达式 scala.Symbol("id") 的简写形式,如果要创建一个包含空格的符号,可以使用 Symbol.apply ,比如 Symbol(" programming Scala") 。[P50]

    函数字面量

    • (i: Int, s: String) => s + i 是一个类型为 Function2[Int, String, String] 的函数字面量。以下声明等价:[P50]

      val f1: (Int, String) => String = (i, s) => s + i
      val f2: Function2[Int, String, String] = (i, s) => s + i
      

    元组字面量

    • Scala 库中包含 TupleN 类,用于组建 N 元素组,它以小括号加上逗号分隔的元素序列的形式来创建元素组。TupleN 表示的多个类各自独立,N 的取值从 1 到 22 ,包括 22 。[P50]

    • 用字面量语法声明 Tuple 类型的变量:[P50]

      val t1: (Int, String) = (1, "two")
      val t2: Tuple2[Int, String] = (1, "two")
      
    • 使用元组:[P51]

      val t = ("Hello", 1, 2.3)
      println("Print the whole tuple: " + t)
      println("Print the first item: " + t._1)
      println("Print the second item: " + t._2)
      println("Print the third item: " + t._3)
      
    • 表达式 t._n 提取元组 t 中的第 n 个元素。元组从 1 开始计数。[P51]

    • 一个量元素的元组,有时被简称为 pair ,有很多定义 pair 的方法,除了在圆括号中列出元素值以外,还可以”箭头操作符“放在两个值之间,也可以用相应类的工厂方法:[P51]

      (1, "one")
      1 -> "one
      Tuple2(1, "one")
      

      箭头操作符只适用于两元素的元组。

    OptionSomeNone :避免使用 null

    • 抽象类 Option 具有两个两个具体的子类 SomeNoneSome 用于表示有值,None 用于表示没有值。[P52]

    • 举个例子:[P52]

      val stateCapitals = Map(
          "Alabama" -> "Montgomery",
          "Alaska" -> "Juneau"
      )
      stateCapitals.get("Alabama") // Some(Montgomery)
      stateCapitals.get("Alabama").get // "Montgomery"
      stateCapitals.get("Unknown") // None
      stateCapitals.get("Unknown").getOrElse("Oops") // "Oops"
      

      Map.get 方法返回 Option[T],本例子中 TString

    封闭类的继承

    • 关键字 sealed 用于实现封闭类的继承。[P53]

      sealed abstract class Option[+A] ... { ... }
      

      关键字 sealed 告诉编译器,所有的子类必须在同一个源文件中声明。

    • 如果为了防止用户派生任何子类,也可以用 final

    用文件和命名空间组织代码

    • Scala 支持嵌套 package 语法:[P54 - 55]

      package org {
          ...
          package scala {
              ...
              package demo {
              ...
              }
          }
      }
      package com {
          package example {
              ...
          }
      }
      package com1.example2 {
          ...
      }
      
    • 使用连续包声明时,必须使用单独的 package 语句:[P55]

      package com.example // 导入 example 中所有包级别的声明
      package mypkg // 导入 mypkg 中所有包级别的声明
      class MyPkgClass {
          ...
      }
      
    • Scala 不允许在脚本中定义包,脚本被隐含包装在一个对象中。在对象中声明包是不允许的。[P55]

    导入类型及其成员

    • 在 Scala 中,使用 _ 作为通配符。[P56]

    • Scala 的 import 语句几乎可以放在任何位置上,所以你可以将其可见性限制在需要的作用域中。[P56]

    • 可以在导入时对类型做重命名以解决冲突问题:[P56]

      import java.math.BigInteger.{
          ONE => _, // 重命名为下划线,使该常量不可见。
          TEN,
          ZERO => JAVAZERO }
      

    导入是相对的

    import collection.immutable._ // 由于 scala 已经默认导入,所以不需要给出全路径
    import _root_.scala.collection.parallel._ // 从 ”根“ 开始的全路径
    

    使用第二种导入方法时,要保证库的所在路径被包含在了 CLASSPATH 中。[P57]

    包对象

    • Scala 支持包对象一种特殊类型的、作用域为包层次的对象。它像普通的对象一样声明,但与普通对象有着一些不同点:[P57]

      • 文件名必须是 package.scala 。 - 1
      • 标记上层包的作用域。 - 2
      • 使用 package 关键字给包名之后的对象命名。 - 3
      • 适合暴露给客户端的成员。 - 4
      // src/com/example/json/package.scala // 1
      package com.example // 2
      package object json { // 3
          class JSONObject { ... } // 4
          def fromString(string: String): JSONObject = { ... }
      }
      // 客户端可以使用 import com.example.json._ 导入所有定义,或用通常方法单独导入元素。
      

    抽象类型与参数化类型

    1. Scala 的参数化类型与 Java 的泛型很像。举一个参数化类型的例子:[P58]

      • 在 Scaladoc 中,List 的声明为 sealed abstract class List[+A]
      • A 之前的 + 表示:如果 BA 的子类型,则 List[B] 也是 List[A] 的子类型,这被称为协类型。
      • 如果类型参数前有 - ,则表示另一种关系:如果 BA 的子类型,且 Foo[A] 被声明为 Foo[-A] ,则 Foo[B]Foo[A] 的父类型(称为逆类型)。
    2. Scala 还支持一种被称为”抽象类型“ 的抽象机制,可以用在许多参数化类型中。[P58]

    3. 参数化类型和抽象类型都被声明为其他类型的成员,就像是该类型的方法与属性一样。[P58 - 59]

      // 类型成员( type )
      abstract class BulkReader {
          type In
          val source: In
          def read: String // 不同的 In ,read 的方式不同。
      }
      class StringBulkReader extends BulkReader {
          type In = String
      }
      
      // 参数化类型
      abstract class BulkReader[In] {
          val source: In
          ...
      }
      class StringBulkReader extends BulkReader[String] = { ... }
      
    4. 类型成员比参数化类型的优势:[P59]

      • 当类型参数与参数化类型无关时,参数化类型更适用。
        • 举个例子:List[A]A 可能是 IntString 等。
      • 当类型成员与所封装的类型同步变化时,类型成员更适用。有时这种特点被称为家族多态,或者协特化。

    相关文章

      网友评论

          本文标题:《Scala 程序设计》学习笔记 Chapter 2:更简洁 更

          本文链接:https://www.haomeiwen.com/subject/vufwnttx.html