Content Table

Scala 语法摘要

Scala: Scalable language, 面向对象的函数式编程语言.

类型

类型的首字母大写, 没有基本类型:

  • Boolean
  • Byte
  • Char
  • Short
  • Int
  • Long
  • Float
  • Double
  • String
  • Unit (即是 void)
  • Any
  • AnyRef
  • AnyValue

变量

常量: val greeting: String = "Hello world" (value), val π = 3.1415926

变量: var greeting: String = "Hello world" (variable)

名字: 字母, 数字, 特殊操作符如 +, -, *, /, π, θ 等, 不能以数字开头

字符串

  • val greeting: String = "Hello world"

  • 多行字符串使用 3 个双引号

    1
    2
    3
    val text = """He is
    a good
    man"""
  • 字符串内插值 (s 开头):

    1
    val text = s"First Name $firstName, Last Name: ${lastName}"
  • 字符串格式化 (f 开头):

    1
    2
    3
    // %s10 不足 10 个字符前面用空格补齐
    // %-10s, 后面补齐
    val text = f"First Name $firstName%3s, Last Name: ${lastName}%10s"
  • 反斜杠不转义:

    1
    val text = raw"Hello \n World" // 也可以使用多行字符串的方式输出 \n, 而不是回车, 多行字符串中也不进行转义

正则表达式

字符串结尾用 .r (即调用字符串的 r() 函数) 构造 Regex 对象, 由于正则表达式中常有 \, 所以推荐用不需要转义的字符串来创建正则表达式的对象, 即使用 raw 字符串或者多行字符串的方式:

1
2
3
4
5
6
7
val date = raw"(\d{4})-(\d{2})-(\d{2})".r
val date = """(\d{4})-(\d{2})-(\d{2})""".r

val input = "Enjoying this apple 3 times today"
val pattern = """.* apple ([\d]+) times .*""".r
val pattern(amountText) = input // 捕获这里需要注意, 变量的定义比较奇怪
val amount = amountText.toInt

运算符重载

Scala 里的运算符例如 1 + 2 中的加其实是函数调用 1.+(2), 再入 1 to 10, 1 until 10, 这几个例子为一个参数的函数调用, 请和下面的无参函数调用进行对比.

无参函数调用

没有参数的函数调用可以如下 3 中方式, 推荐第 1 种方式, 就显示直接访问属性一样:

1
2
3
4
5
3.getClass // 定义时没加 ()
3 getClass
3.getClass()

"23.45".toDouble

元组

元组是 2 个或者多个值的有序容器, 提供了一种建立数据结构的通用方法 (函数返回值)

1
2
3
4
5
6
7
8
val info = (5, "Alice", true), info._1, info._2

def tuple(): (Int, String, Boolean) = {
(1, "Alice", true)
}

val user = tuple()
println(user._1 + ", " + user._2)

表达式

表达式是执行后会返回一个值的代码单元, 可以是单个语句, 也可以是代码块, 函数是命名的表达式, if else, for 循环都是表达式 (while 不是表达式):

  • 字面量是表达式, 例如 5, “Alice”, true
  • if else 表达式 (没有三元运算符, 可以用 if else 实现): val max = if (a > b) a else b
  • 表达式块 {} 最后一个表达式的值为表达式块的返回值, 可以省略 return

match case

Scala 里没有 switch case, 有与之相似的 match case:

  • 不需要 break
  • 多个匹配可以使用 |
  • 可匹配值, 类型, 正则, 数值范围等
  • 可以使用 if 进行条件判断: 模式哨位匹配 pattern guard
  • 没有 default, 取而代之的是 other 或者 _

单个匹配:

1
2
3
4
5
6
7
8
val message = status match {
case 200 => "ok"
case 400 => {
println("ERROR - ...")
"error"
}
case other => "unknown" // 这里使用了值绑定, 也可以用通配符绑定 case _ =>
}

多个匹配:

1
2
3
4
val kind = day match {
case "MON" | "TUE" | "WEN" | "THU" | "FRI" => "weekday"
case "SAT" | "SUN" => "weekend"
}

模式哨位匹配 pattern guard:

1
2
3
4
val result = response match {
case s if s != null => println("Not null")
case s => println("Is null")
}

For 循环

For 循环, 使用 <-, to util 和 range, for 循环是表达式, 能用 yield 来产生值:

1
2
3
4
5
6
7
8
9
10
// Java: for (String item : items)
for (x <- 1 to 5) // [1, 5]
for (x <- 1 until 5) // [1, 5)
for (n <- ns) // ns 是一个集合
val ns = for (n <- 1 to 5) yield { n } // yield 返回不可修改的集合: 1 到 5 的集合
for (n <- 1 to 20 if n%3==0) yield n // 迭代器过滤器 (哨卫)

for(result <- Range(0, 20, 2)) { // 步长为 2, 不包含 20
print(result + " ")
}

默认没有提供 break, 想使用 break 的话需要导入:

1
2
3
4
5
6
import util.control.Breaks.break

for (n <- 0 to 100) {
if (n > 5) break
println(n)
}

集合

1
2
3
val ns: Array[Int] = new Array[Int](20) // 20 是大小
val ns = new Array[Int](20) // mutable
val list: Lit[Int] = List(1, 2, 3, 4) // immutable, Nil, 1 :: list

函数和方法

函数是命名表达式:

  • 函数定义形式: def main(args: Array[String]): Unit = {}

  • 使用表达式块调用函数:

    1
    2
    pow(4)    // 普通方法调用
    pow {2*2} // 先计算 2*2 得到 4, 然后 4 作为函数 pow 的参数进行调用, 省去定义中间变量
  • 使用命名参数调用函数:

    1
    greet(name = "Brown", prefix = "Mr") // 和 OC 的一样, 参数顺序可以和定义的顺序不一致
  • 函数可以有默认参数

    1
    2
    3
    4
    5
    @annotation.tailrec
    def fractional(n: Int, t: Int = 1): Int = {
    if (n <= 1) t
    else fractional(n-1, t*n)
    }
  • 函数可以为可变参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Scala: 类型后面跟上 *
    def sum(items: Int*): Int = {
    var total = 0
    for (item <- items) total += item
    total
    }

    // Java: 类型后面跟上 ...
    public static int sum(int... items) {
    int total = 0;

    for (int item : items) {
    total += item;
    }

    return total;
    }
  • 函数柯里化 (Currying 咖喱):

    把一个参数列表里的参数拆分到多个括号中

    1
    2
    3
    4
    def add(a: Int, b: Int): Int = a + b // add(2, 3)
    def add(a: Int)(b: Int): Int = a + b // add(2)(3)

    def add(a: Int): Int=>Int = (b: Int) => a + b // 分析: 柯里化的函数相当于函数返回值为函数
  • 尾递归函数 (最后一个语句是递归的函数调用), 使用注解 @annotation.tailrec 标记函数为尾递归函数, 不满足的函数会抛出编译错误:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 非尾递归函数
    def fractional(n: Int): Int = {
    if (n <= 1) 1 // 返回 1
    else n * fractional(n - 1) // 对递归的结果还进行了计算
    }

    // 尾递归函数, 结果在结束递归处返回, 并且中间计算结果作为参数传给下一层调用
    @annotation.tailrec
    def fractional(n: Int, t: Int = 1): Int = {
    if (n <= 1) t // 返回 t
    else fractional(n-1, t*n) // 最后一个语句是递归的函数调用
    }
  • 函数嵌套定义:

    1
    2
    3
    4
    def max(x: Int, y: Int, z: Int): Int = {
    def max(a: Int, b: Int): Int = if (a > b) a else b // 和外层的函数虽然同名, 但参数不同, 不会冲突
    max(x, max(y, z))
    }
  • 函数: 使用 val 和 var 定义, 可以简单的理解为 Lambda, 可以作为方法的参数, 有 2 中定义方式:

    1
    2
    3
    4
    5
    6
    7
    val add: (Int, Int) => Int = (a, b) => a + b // 函数形参常用的方式, 以及 Lambda 提供实现, 也叫 FunctionN(N 最大 22, 参数个数)
    val add = (a: Int, b: Int): Int => a + b // 和方法的定义有点像

    // Lambda 的使用: 参数为函数
    def foo(f: (Int, Int) => Int, a: Int, b: Int): Int = f(a, b) // 有 3 个参数: 第一个为函数, 第二三个为整数
    println(foo(_ + _, 3, 4)) // 直接写函数体, 更简洁, 缓存函数的动画计算部分使用这种方式会非常舒服
    println(foo((a, b) => a + b, 3, 4)) // 完整形式

    Scala 函数作为参数比 Java 的 Lambda 直观 (@FunctionalInterface), Scala 直接写函数的签名, 一目了然的看到函数的参数和返回类型, 而 Java 的参数为接口类型, 需要先定义接口, 打开接口的源码或者文档才知道对应方法的签名.

    Scala 中函数和方法不完全一样, 很多时候可以混用, 因为编译器会把方法转换为函数 (方法执行 _ 后变为函数, 例如 add _).

  • 函数的类型:

    是输入类型和输出类型的一个简单组合, 由一个箭头从输入类型指向输出类型, 例如 def double(x: Int): Int = x + 2 的函数类型为 Int=>Int(Int)=>Int (只有一个参数时可以省略括号), 函数作为参数时会用到函数类型定义形参.

  • Lambda 的占位符语法:

    是 Lambda 的一种缩写形式, 讲命名参数替换为通配符 _, 按顺序替换参数, 每个参数最多使用一次

    1
    2
    3
    4
    // Lambda 的使用: 参数为函数
    def foo(f: (Int, Int) => Int, a: Int, b: Int): Int = f(a, b) // 有 3 个参数: 第一个为函数, 第二三个为整数
    foo(_ + _, 3, 4) // 直接写函数体, 更简洁, 缓存函数的动画计算部分使用这种方式会非常舒服
    foo((a, b) => a + b, 3, 4) // 完整形式
  • 方法: 使用 def 定义:

    1
    def add(a: Int, b: Int): Int = a + b
  • 函数使用 => 定义, 方法使用 =

    Java 里 Lambda 使用 ->, JS 里 Lambda 使用 =>, Scala 里 Lambda 使用 =>

  • 高阶函数: 将其他函数作为参数或者返回值为函数的函数

    使用表达式块调用高阶函数 (传名调用: The => Type notation stands for call-by-name), 柯里化的时候调用也更舒服

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    def main(args: Array[String]): Unit = {
    val result = timer {
    util.Random.setSeed(System.currentTimeMillis)
    for (i <- 1 to 10000) util.Random.nextDouble
    util.Random.nextDouble
    }

    println(result)
    }

    // 注意:
    // 参数不是 f: ()=> A, 这个是 Lambda
    // 参数是 =>A, 表示传名调用
    // What's the difference between => , ()=>, and Unit=>: https://stackoverflow.com/questions/4543228/whats-the-difference-between-and-unit
    def timer[A](f: => A): A = {
    val start = System.currentTimeMillis
    val a = f // 函数执行
    val end = System.currentTimeMillis
    println(s"Executed in ${end - start}ms")
    a
    }
  • 偏应用函数 (Partial Applied Function, 又叫部分应用函数)

    指一个函数有 n 个参数, 而我们为其提供少于 n 个参数, 那就得到了一个部分应用函数。

    如果函数传递所有预期的参数, 则表示完全应用它 (完全参数应用函数), 如果只是传递几个参数并不是全部参数, 那么将返回部分应用的函数, 这样可以方便的绑定一些参数, 其余的参数可稍后填写补上

    1
    2
    3
    4
    def full(a: Int, b: Int): Int = a + b
    val part1 = full(10, _: Int) // 简写
    val part2 = (x: Int) => full(10, x) // 函数调用
    val part3: Int => Int = full(10, _: Int) // 写上返回类型
  • 偏函数, 参考 https://www.jianshu.com/p/0a8a15dbb348, http://zhuanlan.51cto.com/art/201703/534053.htm

    Scala 中的 Partial Function 是一个 Trait,其类型为 PartialFunction[A, B],其中接收一个类型为 A 的参数,返回一个类型为 B 的结果.

    如果一组 case 语句没有涵盖所有的情况,那么这组 case 语句就可以被看做是一个偏函数, case 语句从本质上讲就是 PartialFunction 的子类。偏函数中最常见的组合方法为 orElse、andThen 与 compose, orElse 相当于一个或运算,如果通过它将多个偏函数组合起来,就相当于形成了多个 case 合成的模式匹配。倘若所有偏函数满足了输入值的所有分支,组合起来就形成一个函数了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 求绝对值的运算
    val positiveNumber: PartialFunction[Int, Int] = {
    case x if x > 0 => x
    }
    val zero: PartialFunction[Int, Int] = {
    case x if x == 0 => 0
    }
    val negativeNumber: PartialFunction[Int, Int] = {
    case x if x < 0 => -x
    }

    def abs(x: Int): Int = (positiveNumber orElse zero orElse negativeNumber)(x)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scala> val pf:PartialFunction[Int,String] = {
| case 1=>"One"
| case 2=>"Two"
| case 3=>"Three"
| case _=>"Other"
| }
pf: PartialFunction[Int,String] = <function1>

scala> pf(1)
res0: String = One

scala> pf(2)
res1: String = Two

scala> pf(3)
res2: String = Three

scala> pf(4)
res3: String = Other

艺术地说,Scala 中的 Partial Function 就是一个“残缺”的函数,就像一个严重偏科的学生,只对某些科目感兴趣,而对没有兴趣的内容弃若蔽履。Partial Function 做不到以“偏”概全,因而需要将多个偏函数组合,最终才能达到全面覆盖的目的。

利用 andThen 组合偏函数,设计本质接近 Pipe-and-Filter 模式,每个偏函数都可以理解为是一个 Filter。因为要将这些偏函数组合起来形成一个管道,这就要求被组合的偏函数其输入值与输出值必须支持可串接,即上一个偏函数的输出值会作为下一个偏函数的输入值。

  • 泛型: 使用 [T] 定义泛型

    1
    2
    3
    4
    5
    6
    7
    8
    def generic[A](a: A): Unit = {
    println(a)
    }

    // 泛型函数调用
    generic(1)
    generic("String")
    generic[String]("String")

数组

和 Java 一样, 数组 Array 的长度是不可变的:

1
2
3
4
5
6
val arr: Array[Int] = new Array[Int](3)
arr(0) = 100
println(arr.toBuffer)

val arr = new Array[Int](3)
val arr = Array(1, 2, 3)

Array 几个重要的方法有 foreach, map, filter, flatten, flatMap, groupBy, reduce, fold, mkString:

  • foreach: 遍历数组的每一个元素

    1
    2
    3
    4
    5
    arr1.foreach(println)

    for (e <- arr1) {
    println(e)
    }
  • map: 映射, 把一个元素映射为另外一个元素, 返回一个新的数组, 不影响原来的数组, 元素的类型可以改变

    1
    2
    3
    4
    5
    // 把每一个元素乘以 10
    val arr1 = Array(1, 2, 3, 4) // 初始化
    val arr2 = arr1.map((e: Int) => e * 10)
    val arr3 = arr1.map(e => e * 10)
    val arr4 = arr1.map(_ * 10)
  • filter: 传入一个 predication Lambda, 过滤留下符合条件的元素

    1
    2
    val arr1 = Array(1, 2, 3, 4, 5)
    val arr2 = arr1.filter(_ > 3)
  • flatten (扁平化, 将列表的列表转换为列表): 数组的元素是数组, 使用 flatten 把元素给展开放在新数组里

    1
    2
    3
    val arr1 = Array(Array(1, 2, 3), Array(4, 5, 6))
    var arr2 = arr1.flatten
    println(arr2.toBuffer)
  • flatMap: 先 map 再 flatten

    1
    2
    3
    4
    5
    6
    7
    8
    9
    val arr1 = Array("Alice Bob John", "One Two Three")
    val arr2 = arr1.flatMap(_.split(" ")) // lambda 执行后的结果是一个数组,然后执行 flatten (作用于列表的列表)
    val arr3 = arr1.map(_.split(" ")).flatten // _.split(" ") 的结果是一个数组

    println(arr2.toBuffer)
    println(arr3.toBuffer)

    ArrayBuffer(Alice, Bob, John, One, Two, Three)
    ArrayBuffer(Alice, Bob, John, One, Two, Three)
  • groupBy: 把数组中的元素分组, 返回一个 Map, key 为元素传入 Lambda 计算后的结果, value 为有相同 key 的元素的 List

    1
    2
    3
    4
    5
    val arr = Array("Five Four Three Two Two", "One Two Three")
    val map = arr.flatMap(_.split(" ")).groupBy(e => e) // 函数对每个元素计算的结果作为 key, 元素作为此 key 对应数组的元素

    // 统计单词的数量, 使用 mapValues: 对 Map 的 values 进行映射, key 不变, 值为 key 对应 values 计算的结果
    arr.flatMap(_.split(" ")).groupBy(e => e).mapValues(e => e.length) // 输出: Map(One -> 1, Five -> 1, Two -> 3, Four -> 1, Three -> 2)
  • reduce: 讲数组折叠为单个元素

    第一个参数为累积结果, 第二个参数为数组的元素

    1
    2
    3
    4
    5
    val ns = Array(1, 2, 3, 4, 5)
    ns.reduce((accumulation, e) => {
    println(accumulation + ", " + e)
    accumulation + e
    })

不可变集合

List, Set, Map, 下标从 0 开始:

1
2
3
4
5
6
7
8
9
val list = List(1, 2, 3, 4)  // list(0), list(1), list.size, Nil
val set = Set(1, 2, 1, 2, 3)
val map = Map("red" -> 0xFF0000, "green" -> 0x00FF00, "blue" -> 0x0000FF)
val red = map("red") // 获取 map 的 value

// 定义时指定类型
val list: List[Int] = List(1, 2, 3, 4)
val set: Set[Int] = Set(1, 2, 1, 2, 3)
val map: Map[String, Int] = Map("red" -> 0xFF0000, "green" -> 0x00FF00, "blue" -> 0x0000FF)
  • list: List() 创建一个空 List, 等于 Nil (List[Nothing] 的单例对象), val list2 = 2 :: list1 在 list1 头部插入 2 创建新的 List

  • List(1, 2, 3) 调用的是 List.apply(1, 2, 3) 返回一个 List 对象, 只是一个语法糖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    object Hello {
    def apply(): Unit = {
    println("applied")
    }

    def main(args: Array[String]): Unit = {
    Hello()
    }
    }

单元素集合

  • Option
  • Try
  • Future

构造函数有 3 中: 默认构造函数, 主构造函数, 辅助构造函数, 伴生对象也是很重要的特性:

  • 默认构造函数:

    1
    2
    3
    4
    class Teacher {
    var name: String = _ // _ 会为根据类型进行初始化, 字符串为 null, 整数为 0
    var age: Int = _ // 属性也可以用 private 修饰
    }
  • 主构造函数: 主构造函数中 var | val 修饰的形参自动为类的成员变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 主构造函数中 var | val 修饰的形参自动为类的成员变量
    class Teacher(var name: String, var age: Int) {

    }

    // 创建类的对象和访问成员变量
    var teacher = new Teacher("Alice", 22)
    println(teacher.name)

    // 在主构造函数前加 private 则外界不能访问, 只能辅助构造函数访问, 形参也可以用 private 修饰
    class Teacher private (var name: String, private var age: Int) {}

    // 限制类的访问范围
    private[this] class Teacher {} // 当前包下可访问
  • 辅助构造函数: 可以有多个辅助构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Teacher(name: String, age: Int) {
    var gender: Boolean = _

    // def this 定义辅助构造函数, 必须先调用主构造函数或者其他辅助构造函数
    def this(name: String, age: Int, gender: Boolean) = {
    this(name, age)
    this.gender = gender
    }
    }
  • 伴生对象: 类的同名对象, 可以用伴生对象的 apply() 函数创建对象, 例如 List(1, 2, 3)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Teacher (var name: String, var age: Int) {

    }

    // 类 Teacher 的伴生对象
    object Teacher {
    def apply(): Teacher = {
    new Teacher("Alice", 22)
    }
    }

    println(Teacher().name) // 使用伴生对象调用 apply 创建 Teacher 的对象
  • 对象变量名后跟上 () 即调用 apply() 函数, 例如 List 对象访问下标指定的元素 list(3), apply() 方法实际是一个快捷方式, 可以使用小括号触发而不需要方法名:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // List 下标访问元素
    val list = List(0, 1, 2, 3)
    list(2)

    // 类定义
    class Multiplier(factor: Int) {
    def apply(input: Int) = input * factor
    }

    val tripleMe = new Multiplier(3)
    tripleMe.apply(10) // 返回 30
    tripleMe(10) // 返回 30, 等于 tripleMe.apply(10)
  • 抽象类: 使用 extends 实现继承 (trait 也是使用 extends 进行实现)

    1
    2
    3
    4
    5
    6
    7
    abstract class AbsClass {
    def foo(): Unit
    }

    class AbsClassImpl extends AbsClass {
    override def foo(): Unit = println("AbsClassImpl")
    }
  • case class: 可以使用 match 模式匹配, 默认实现了 Serializable 接口 (case object 也一样, 不需要封装数据的就用 case object)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    case class Bomb(name: String, sn: String) // 空的类体 {} 可要可不要
    case object Manufacture

    def matchTest(obj: Any): Unit = {
    obj match {
    case Bomb("2", "2") => println("2-2")
    case Bomb(x, y) => println("Bomb")
    case Manufacture => println("Manufacture")
    }
    }

    def main(args: Array[String]): Unit = {
    matchTest(Bomb("One", "ONe")) // case class 类创建对象可以不需要 new
    matchTest(new Bomb("2", "2"))
    matchTest(Manufacture)
    }

接口 trait

Scala 中把 trait 叫做特质, 其实就是接口, 相当于 Java 中的 @FunctionalInterface, 可以定义有实现和没有实现的方法, object 和 class 都能扩展 trait, 只有 extends, 没有 implements, 使用 override 避免重写错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
trait Flyable {
def fly(): Unit
def to(): Unit = {
println("flyable.to()")
}

def from(): Unit = {
println("Flyable.from()")
}
}

object Bird extends Flyable {
override def fly(): Unit = {
println("Bird.fly()")
}

override def to(): Unit = {
println("Bird.to()")
}
}

// 使用
Bird.fly() // Bird.fly()
Bird.to() // Bird.to()
Bird.from() // Flyable.from()

创建对象的时候可以动态的使用 with 混入 trait:

1
2
3
4
5
6
7
8
9
10
def main(args: Array[String]): Unit = {
var teacher = new Teacher("Alice", 22) with Flyable {
override def fly(): Unit = println("Trait mixed in Teacher")
}

teacher.fly() // Teacher 有了 Flyable 的功能
}

// 可以动态混入多个 trait
new Teacher("Alice", 22) with Flyable with Foo {}

类定义的时候 extends traits:

1
2
3
4
class Teacher (var name: String, var age: Int) extends Flyable with Foo {
override def fly(): Unit = println("Trait mixed in Teacher")
override def foo(): Unit = println("Teacher foo")
}

隐式类型转换

当编译器看到需要类型 X 却给了类型 Y, 它就在当前作用域查找是否定义了从类型 Y 到类型 X 的隐式定义, 如果有则转换类型 Y 为 X, 否则编译器报错:

  1. implicit def File2MagicFile(file: File): MagicFile = new MagicFile(file)
  2. val file: MagicFile = new File("data.txt")
  3. 这时 file 就自动转换为 MagicFile 的对象了, 同时具有 File 和 MagicFile 的行为

下面把 Double 隐式转换为 Int:

1
2
3
implicit def double2Int(x: Double): Int = x.toInt // Double 转换为 Int
var n: Int = 2.34 // 如果没有上面的 double2Int 则编译时报错
println(n)

隐式类型转换的代码可以放到一个 object 中 (不是 class 中), 然后在使用隐式转换的代码处导入这个 object 的文件, 这样可以统一的管理隐式转换:

1
2
3
4
5
6
7
8
9
10
// Implicits.scala
object Implicits {
// 1. 隐式转换的函数
implicit def double2Int(x: Double): Int = x.toInt

// 2. 隐式转换的类 (这种类只能在 object 中定义)
implicit class FileReader(file: File) {
def size: Long = file.length
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// Hello.java
object Hello {
def main(args: Array[String]): Unit = {
import Implicits.double2Int // 导入隐式转换的代码到上下文中
var n: Int = 2.34
println(n)

import Implicits.FileReader
val file = new java.io.File("/Users/Biao/Desktop/apple.png")
println(file.size)
}
}

隐式转换有 3 种使用方式:

  • 隐式参数: 当我们在定义方法时,可以把最后一个参数列表标记为implicit,表示该组参数是隐式参数。一个方法只会有一个隐式参数列表,置于方法的最后一个参数列表。如果方法有多个隐式参数,只需一个implicit修饰即可。 当调用包含隐式参数的方法是,如果当前上下文中有合适的隐式值,则编译器会自动为改组参数填充合适的值。如果没有编译器会抛出异常。当然,标记为隐式参数的我们也可以手动为该参数添加默认值
  • 隐式地转换类型: 使用隐含转换将变量转换成预期的类型是编译器最先使用 implicit 的地方。这个规则非常简单,当编译器看到类型X而却需要类型Y,它就在当前作用域查找是否定义了从类型X到类型Y的隐式定义
  • 隐式调用函数: 隐式调用函数可以转换调用方法的对象,比如但编译器看到X .method,而类型 X 没有定义 method(包括基类)方法,那么编译器就查找作用域内定义的从 X 到其它对象的类型转换,比如 Y,而类型Y定义了 method 方法,编译器就首先使用隐含类型转换把 X 转换成 Y,然后调用 Y 的 method

泛型

1
2
3
4
5
6
class Generic[T](vox: T) {

}

class Foo[B >: A] // BA 的祖先
class Foo[B <: A] // BA 的后代

枚举

Scala 中没有枚举, 但可以继承 Enumeration 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object Colors extends Enumeration {
type Colors = Value // [1] 声明枚举对外暴露的变量类型
val RED, GREEN, BLUE = Value // [2.1] 存储的值为变量名

// [2.2] 指定存储的值
// val RED = Value("red")
// val GREEN = Value("green")
// val BLUE = Value("blue")
}

def main(args: Array[String]): Unit = {
val color = Colors.GREEN

color match {
case Colors.RED => println(color)
case Colors.GREEN => println(color)
case Colors.BLUE => println(color)
}
}

Misc

Scala 中没有 static, object 中定义的内容都可以直接调用 (object 是一个单例对象, 不能 new).

为类型定义别名使用 type (和 C 中的 typedef 一个作用): type M = collection.mutable.HashMap[Int, String]; val m: M = new M().

导入时给类型重命名:

1
2
3
4
5
import java.util.{HashMap => JavaHashMap}
val m = new JavaHashMap[Integer, String]()
m.put(1, "One")
m.put(2, "Two")
m.get(1)

排序用的 Ordered 相当于 Java 的 Comparable, Ordering 相当于 Java 的 Comparator.

根据 object 的 lazy 变量的表达式部分只执行一次的特点执行初始化工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
object Config {
lazy val url: String = {
// 初始化工作
println("Loading config from file");

"loaded-url"
}
}

object Hello {
def main(args: Array[String]): Unit = {
println(Config.url) // 执行初始化部分的代码
println(Config.url) // 不会再执行初始化部分
}
}

函数 apply(), unapply(), update()

  • apply: 参数到对象, 例如 List(1, 2, 3) 创建对象
  • unapply: 解构对象到变量, 按照主构造函数的参数顺序, match case 常用, 如 case Currency(amount, "USD") => println("USD: " + amount)
  • update: 更新操作, map(1) = "One" 等价于 map.update(1, "One")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
object AppDemo {
def main(args: Array[String]): Unit = {
val car = Car("BZ", 123)
val Car(brand, price) = car // 解构时创建变量
println(s"Brand: $brand, Price: $price")

car match {
case Car(sbrand, 1) => println(sbrand)
case Car(sbrand, 2) => println(sbrand)
case Car(sbrand, sprice) => println(sprice)
}
}

}

// 伴生类可用于 match case, 按照主构造函数的参数顺序创建变量
case class Car(var brand: String, var price: Int)

// 如无特殊情况, 下面的代码可不用, 会自动生成: Scala 为每一个 case class 自动生成一个伴生对象, 其中自动生成 apply() 和 unapply() 方法
case object Car {
def apply(brand: String, price: Int) = new Car(brand, price)

def unapply(car: Car): Option[(String, Int)] = Some(car.brand -> car.price)
}

什么是尾递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
参考什么是尾递归 https://www.zhihu.com/question/20761771

递归函数
def recsum(x):
if x == 1:
return x
else:
return x + recsum(x - 1)

当调用recsum(5),Python调试器中发生如下状况:
recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
5 + (4 + (3 + 3))
5 + (4 + 6)
5 + 10
15

定义为尾递归
def tailrecsum(x, running_total=0):
if x == 0:
return running_total
else:
return tailrecsum(x - 1, running_total + x)

当调用recsum(5),Python调试器中发生如下状况:
tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15