Skip to content

Scala 速查表

运行于 JVM 上的函数式/面向对象混合语言。

01

基础

变量与类型

优先使用 val(不可变)而非 var(可变),以获得更安全、更可预测的代码。Scala 会进行类型推断,但显式标注有助于提升公共 API 的可读性。一切都是对象——没有原始类型(Int、Boolean 都是类)。

scala
val name = "Alice"   // immutable (preferred)
var age = 30          // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List[Int] = List(1, 2, 3)
println(name.getClass)  // class java.lang.String

字符串插值

s"..." 启用 ${expr} 插值。f"..." 添加 printf 风格的格式化(%s、%.2f)。raw"..." 禁用转义序列。这些前缀使字符串构建类型安全且可读。

scala
val name = "Alice"
val age = 30
println(s"Name: ${name}, Age: ${age}")
println(s"Next year: ${age + 1}")
println(s"Upper: ${name.toUpperCase}")
println(f"${name}%s weighs ${65.5}%.1f kg")  // formatted
println(raw"No \n escape")  // raw string

元组

元组将 2-22 个异构值组合在一起。通过 _1、_2(从 1 开始索引)访问。使用 val (a, b) = tuple 解构。对于超过 2 个元素的情况,优先使用 case class 以获得命名字段和更好的可读性。

scala
val pair = ("Alice", 30)
println(pair._1)  // Alice
println(pair._2)  // 30
val (name, age) = pair  // destructure
println(s"${name}: ${age}")
// Scala 3: val p = ("a", 1, 2.0)
val triple = ("a", 1, 2.0)
println(triple._3)  // 2.0

类型推断与类型标注

Scala 会为局部变量和返回类型进行类型推断。在公共 API、递归函数和歧义情况下使用显式类型。类型标注(expr: Type)强制指定类型——适用于向上转型或消歧。

scala
val x = 42           // Int inferred
val y: Long = 42      // explicit Long
val z = 42: Long      // type ascription
val list = List(1, 2, 3)  // List[Int]
val mixed: List[Any] = List(1, "a", true)
def double(x: Int) = x * 2  // return type inferred

Unit 与 Nothing

Unit 类似 void(只有一个值:())。Nothing 是没有实例的底部类型——用于永不返回的函数(throw、无限循环)。Nothing 是所有类型的子类型,从而实现灵活的类型推断。

scala
def printIt(x: Int): Unit = println(x)  // like void
val u: Unit = ()    // Unit has one value: ()
// Nothing is the bottom type—no instances
def error(msg: String): Nothing =
  throw new RuntimeException(msg)
// Nothing is a subtype of everything
val n: Nothing = error("boom")
02

字符串

常用字符串方法

Scala 字符串是 Java 字符串加上通过隐式转换(StringOps)添加的额外方法。大多数方法返回新字符串(不可变)。使用这些方法而非手动循环,以获得更好的清晰度和正确性。

scala
val s = "Hello, World"
println(s.length)        // 12
println(s.toUpperCase)   // HELLO, WORLD
println(s.toLowerCase)   // hello, world
println(s.split(", "))   // Array(Hello, World)
println(s.replace("o", "0"))  // Hell0, W0rld
println(s.reverse)       // dlroW ,olleH
println(s.contains("World"))  // true

多行字符串(三引号)

三引号字符串保留所有空白和换行。使用 stripMargin 配合 | 来整洁地对齐代码——只保留 | 之后的文本。非常适合嵌入代码中的 SQL、JSON 或模板。

scala
val sql = """
  SELECT * FROM users
  WHERE age > 18
  ORDER BY name
"""
println(sql.trim)
// StripMargin for clean indentation
val text = """|Hello
              |World""".stripMargin
println(text)  // Hello
World

字符串构建

mkString 用可选的前缀/后缀连接集合——既地道又高效。在循环中构建大字符串时使用 StringBuilder。避免在循环中重复使用 + 拼接(会创建大量中间对象)。

scala
val parts = List("apple", "banana", "cherry")
println(parts.mkString(", "))     // apple, banana, cherry
println(parts.mkString("[", ", ", "]"))  // [apple, banana, cherry]
val sb = new StringBuilder
for (p <- parts) sb.append(p).append(" ")
println(sb.toString.trim)

字符串转数字

toInt/toDouble 在无效输入时会抛出异常。使用 toIntOption(Scala 2.13+)进行返回 Option 的安全解析。对于批量解析,使用 Try 或 Either 以函数式方式处理错误而无需异常。

scala
val n = "42".toInt        // 42
val d = "3.14".toDouble    // 3.14
val b = "true".toBoolean   // true
val safe = "abc".toIntOption  // Some(42) or None
// Handling errors
val result = try "x".toInt catch { case _ => 0 }
println(result)  // 0

正则表达式

.r 将字符串转换为 Regex。findFirstIn 返回 Option,findAllIn 返回迭代器。在模式匹配中使用 case email(e) => 进行提取。Regex 底层是 Java 的 Pattern。

scala
import scala.util.matching.Regex
val email: Regex = "[\w.]+@[\w]+\.[a-z]+".r
val text = "Contact: [email protected]"
email.findFirstIn(text) match {
  case Some(e) => println(s"Found: ${e}")
  case None => println("No email")
}
val replaced = "[0-9]+".r.replaceAllIn("a1b2c3", "#")
println(replaced)  // a#b#c#
03

数据结构

List 与 Seq

List 是不可变的单链表——头部/前插为 O(1),随机访问为 O(n)。随机访问请使用 Vector(有效 O(1))。+: 前插,:+ 追加。优先使用不可变集合以保证线程安全。

scala
val nums = List(1, 2, 3, 4, 5)
println(nums.head)      // 1
println(nums.tail)      // List(2,3,4,5)
println(nums.reverse)   // List(5,4,3,2,1)
println(nums.take(2))   // List(1, 2)
println(nums.drop(2))   // List(3, 4, 5)
println(nums.mkString)  // 12345
val combined = 0 +: nums :+ 6  // List(0,1,2,3,4,5,6)

Map

Map 是不可变的——操作返回新的 Map。使用 get(key) 进行 Option 访问,getOrElse 获取默认值。+ 添加/更新,- 删除。对于可变映射,使用 scala.collection.mutable.Map。键必须是可哈希的。

scala
val ages = Map("Alice" -> 30, "Bob" -> 25)
println(ages("Alice"))            // 30 (throws if missing)
println(ages.getOrElse("Eve", 0)) // 0 (safe)
val updated = ages + ("Eve" -> 28)  // new Map
val removed = ages - "Bob"
ages.foreach { case (k, v) => println(s"${k}: ${v}") }
println(ages.keys)  // Set(Alice, Bob)

Set

Set 是不可变的,contains 为 O(1)。union(|)、intersect(&)、diff(~)用于集合运算。+ 添加,- 删除。用于去重和成员检测。可变变体:scala.collection.mutable.Set。

scala
val a = Set(1, 2, 3)
val b = Set(3, 4, 5)
println(a union b)        // Set(1,2,3,4,5)
println(a intersect b)    // Set(3)
println(a diff b)         // Set(1,2)
println(a subsetOf(Set(1,2,3,4)))  // true
val added = a + 6  // Set(1,2,3,6)

Option(空安全)

Option 替代 null——Some(value) 或 None。使用 map/filter/flatMap 进行转换,getOrElse 获取默认值。for 推导式可作用于 Option。这通过在类型中显式表达缺失,消除了 NullPointerException。

scala
def findUser(id: Int): Option[String] =
  if (id == 1) Some("Alice") else None
val name = findUser(1)
println(name.getOrElse("Unknown"))  // Alice
println(name.map(_.toUpperCase))    // Some(ALICE)
println(name.filter(_.startsWith("A")))  // Some(Alice)
val result = for {
  n <- findUser(1)
  if n.startsWith("A")
} yield n.toUpperCase  // Some(ALICE)

Array 与 Vector

Array 是可变的 Java 数组(最快,但没有函数式更新)。Vector 是不可变的,具有有效的 O(1) 随机访问和更新——是不可变随机访问集合的首选。顺序访问用 List,索引访问用 Vector。

scala
val arr = Array(1, 2, 3, 4)  // mutable, Java array
arr(0) = 10
println(arr(0))  // 10
val vec = Vector(1, 2, 3, 4)  // immutable, fast random access
println(vec(2))  // 3
val updated = vec.updated(0, 10)  // Vector(10,2,3,4)
// Vector: O(1) random access + immutable
// Array: mutable, Java interop, fastest
04

控制流

If / Else(表达式)

在 Scala 中,if/else 是返回值的表达式。这消除了对三元运算符的需求。两个分支必须具有兼容的类型。用于简洁的条件赋值。

scala
val score = 85
val grade =
  if (score >= 90) "A"
  else if (score >= 80) "B"
  else if (score >= 70) "C"
  else "F"
println(grade)  // B
// if returns a value—no ternary operator needed

For 推导式

for 推导式可迭代,并能过滤(if 守卫)和转换(yield)。没有 yield 时是循环;有 yield 时构建集合。to 包含终点,until 不包含。等价于 flatMap/map/filter 链。

scala
for (i <- 1 to 5) println(i)  // 1 2 3 4 5
for (i <- 1 until 5) print(i)  // 1 2 3 4
// With yield (generates collection)
val doubled = for (n <- List(1,2,3)) yield n * 2
println(doubled)  // List(2, 4, 6)
// With filter (guard)
val evens = for (n <- 1 to 10 if n % 2 == 0) yield n
println(evens.toList)  // List(2,4,6,8,10)

Match(模式匹配)

match 是 Scala 强大的模式匹配——就像增强版的 switch。支持字面量、OR(|)、守卫(if)、类型匹配和解构。必须穷尽(编译器会警告遗漏的情况)。它是返回值的表达式。

scala
val n = 2
val label = n match {
  case 0 => "zero"
  case 1 | 2 | 3 => "small"
  case x if x < 10 => "medium"
  case _ => "large"
}
println(label)  // small
// Match on types
def describe(x: Any): String = x match {
  case i: Int => s"Int: ${i}"
  case s: String => s"String: ${s}"
  case _ => "unknown"
}

While 与 Do-While

while/do-while 是返回 Unit 的命令式循环。它们需要可变状态(var)。优先使用 for 推导式或递归以获得地道的函数式 Scala。仅在性能要求或副作用场景下使用 while。

scala
var i = 0
while (i < 3) {
  println(i)
  i += 1
}
var j = 0
do {
  println(j)
  j += 1
} while (j < 3)
// Prefer recursion or for-comprehensions for immutability

Try / Catch / Finally

try/catch/finally 类似 Java,但在 catch 中使用模式匹配。优先使用 Try 进行函数式错误处理——它将异常包装为 Success/Failure 值,从而无需 try/catch 样板代码即可进行 map/flatMap 链式调用。

scala
import scala.util.{Try, Success, Failure}
val result = try {
  "abc".toInt
} catch {
  case e: NumberFormatException => 0
} finally {
  println("cleanup")
}
println(result)  // 0
// Functional alternative
val r2 = Try("abc".toInt).getOrElse(0)
println(r2)  // 0
05

函数

方法定义

方法使用 def name(params): ReturnType = body。支持默认参数和命名参数。Unit 返回值 = 仅副作用。单表达式体可省略花括号。= 是必需的(没有它就是返回 Unit 的过程)。

scala
def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
  s"${greeting}, ${name}!"
def log(msg: String): Unit = println(msg)
println(add(3, 4))           // 7
println(greet("Alice"))      // Hello, Alice!
println(greet("Bob", greeting = "Hi"))  // named arg

Lambda(匿名函数)

Lambda:(params) => body。使用 _ 作为单参数的简写(x => x * 2 变为 _ * 2)。多个 _ 引用不同的参数(_ + _)。Lambda 是一等公民——可传递给 map、filter、reduce 等。

scala
val square = (x: Int) => x * x
println(square(5))  // 25
val nums = List(1, 2, 3)
println(nums.map(_ * 2))      // List(2, 4, 6)
println(nums.filter(_ > 1))   // List(2, 3)
println(nums.reduce(_ + _))   // 6
// _ is shorthand for the parameter

高阶函数

高阶函数接收或返回函数。这实现了强大的抽象:map/filter/reduce、组合、部分应用。makeAdder 返回一个捕获 n 的闭包。这是函数式编程的核心。

scala
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(applyTwice(_ + 3, 5))  // 11
def makeAdder(n: Int): Int => Int = _ + n
val add5 = makeAdder(5)
println(add5(10))  // 15
// Functions returning functions = currying-like

柯里化与部分应用

柯里化将参数拆分为多个参数列表:def f(a)(b)。使用 _ 部分应用以创建专用函数。多个参数列表改善类型推断(编译器可从列表推断 f 的类型)。在集合 API 中很常见。

scala
def add(a: Int)(b: Int): Int = a + b  // curried
val add5 = add(5)_  // partially applied
println(add5(3))  // 8
// Multiple parameter lists
def foldLeft[A, B](list: List[A])(z: B)(f: (B, A) => B): B = ???
// Helps type inference
val sum = List(1,2,3).foldLeft(0)(_ + _)

传名参数与 Lazy

传名参数(=> T)在每次使用时延迟求值——适用于日志(禁用时跳过昂贵的 msg)和自定义控制结构。lazy val 将初始化推迟到首次访问时——用于昂贵或可选的值。

scala
// By-name parameter: evaluated on each use
def debug(msg: => String): Unit =
  if (debugEnabled) println(msg)
// Lazy evaluation
lazy val expensive = computeHeavy()
println(expensive)  // computed now
def computeHeavy(): Int = { println("computing"); 42 }
06

类与面向对象

类与构造器

主构造器参数位于类签名中。val 参数成为不可变字段(公共 getter),var 可变。类体就是构造器。辅助构造器使用 def this(...) 且必须调用另一个构造器。

scala
class Person(val name: String, val age: Int) {
  // Constructor params with val/var become fields
  def greet: String = s"Hi, I'm ${name}"
  def isAdult: Boolean = age >= 18
}
val p = new Person("Alice", 30)
println(p.greet)   // Hi, I'm Alice
println(p.name)    // Alice (val field)
println(p.isAdult) // true

Case Class

Case class 是不可变的数据类,自动生成 equals、hashCode、toString、copy 以及带有 apply/unapply 的伴生对象。用于数据建模和模式匹配。无需 'new'。它们是 Scala 中 ADT 的基础。

scala
case class Point(x: Int, y: Int)
val p1 = Point(3, 4)  // no 'new' needed
val p2 = Point(3, 4)
println(p1 == p2)  // true (value equality)
val moved = p1.copy(x = 5)  // Point(5, 4)
println(p1.x, p1.y)  // 3 4 (fields auto-visible)
// Auto: equals, hashCode, toString, copy, companion

Trait(带实现的接口)

Trait 类似 Java 接口但可以有实现。一个类可以混入多个 trait(使用 extends/with)。Trait 实现了行为的多继承。用于共享接口、混入以及通过线性化实现可堆叠的修改。

scala
trait Greetable {
  def name: String  // abstract
  def greet: String = s"Hello, ${name}"  // concrete
}
trait Named {
  val name: String
}
class User(val name: String) extends Greetable
val u = new User("Alice")
println(u.greet)  // Hello, Alice
// Stackable traits via linearization

Object(单例)

object 声明一个单例(一个实例)。伴生对象(与类同名)持有类似静态的方法、工厂(apply)和提取器(unapply)。使用 apply 作为工厂方法,让调用者省略 'new'。这是地道的 Scala 写法。

scala
object Config {
  val version = "1.0"
  def load(): Map[String, String] = Map("key" -> "value")
}
println(Config.version)  // 1.0
// Companion object (same name as class)
class Person(val name: String)
object Person {
  def apply(name: String): Person = new Person(name)
}
val p = Person("Alice")  // uses apply, no 'new'

继承与抽象类

abstract class 可以有未实现的成员。使用 extends 继承,override 重定义。混入优先使用 trait(多继承)。当需要构造器参数或想要基类型时使用抽象类。类只能单继承。

scala
abstract class Shape {
  def area: Double  // abstract
  def describe: String = s"Area: ${area}"
}
class Circle(r: Double) extends Shape {
  def area: Double = math.Pi * r * r
}
val c = new Circle(5)
println(c.describe)  // Area: 78.53...
// override required for concrete members
// abstract class vs trait: use abstract for base, trait for mixins
07

集合与函数式

Map / Filter / Fold

map 转换,filter 选择,reduce/foldLeft 聚合。这些是函数式数据转换的核心。foldLeft 接受种子且具有结合性;reduce 要求非空。使用这些替代循环以获得清晰度和不可变性。

scala
val nums = List(1, 2, 3, 4, 5)
println(nums.map(_ * 2))           // List(2,4,6,8,10)
println(nums.filter(_ % 2 == 0))   // List(2,4)
println(nums.reduce(_ + _))        // 15
println(nums.foldLeft(0)(_ + _))   // 15
println(nums.sum)                  // 15
println(nums.mkString(", "))       // 1, 2, 3, 4, 5

FlatMap 与 For 推导式

flatMap 一步完成映射和扁平化——对嵌套集合和单子操作至关重要。for 推导式是 flatMap/map/filter 链的语法糖。用于复杂的嵌套转换——更易读。

scala
val nested = List(List(1, 2), List(3, 4))
println(nested.flatten)        // List(1,2,3,4)
println(nested.flatMap(_.map(_ * 2)))  // List(2,4,6,8)
// Equivalent for-comprehension:
val result = for {
  inner <- nested
  n <- inner
} yield n * 2
println(result)  // List(2,4,6,8)

分组与排序

groupBy 按键分区为 Map。sorted 自然排序,sortBy 按键函数排序,sortWith 使用比较器。这些返回新集合(不可变)。用于数据分析、分类和排序。

scala
val words = List("apple", "bat", "cat", "ant")
val byFirst = words.groupBy(_.head)
// Map(a -> List(apple, ant), b -> List(bat), c -> List(cat))
println(byFirst)
val sorted = words.sorted  // List(ant, apple, bat, cat)
val byLen = words.sortBy(_.length)  // List(ant, bat, cat, apple)
val desc = words.sortWith(_ > _)  // descending
println(sorted, byLen)

Either 与 Try

Try 将异常包装为 Success/Failure 值。Either 表示 Left(错误)或 Right(成功)——用于需要区分错误类型的领域错误。两者都支持 map/flatMap 进行函数式错误传播。

scala
import scala.util.{Try, Success, Failure}
def parse(s: String): Try[Int] = Try(s.toInt)
parse("42") match {
  case Success(n) => println(s"OK: ${n}")
  case Failure(e) => println(s"Err: ${e.getMessage}")
}
// Either for domain errors
def divide(a: Int, b: Int): Either[String, Int] =
  if (b == 0) Left("div by zero") else Right(a / b)
println(divide(10, 2))  // Right(5)
println(divide(10, 0))  // Left(div by zero)

惰性集合(View 与 Lazy)

.view 使集合变惰性——操作延迟到强制求值时(toList、sum 等)执行。避免中间集合,对大数据性能更好。LazyList(原 Stream)支持无限序列——元素按需计算。

scala
val nums = (1 to 1000000).view
val result = nums
  .filter(_ % 2 == 0)
  .map(_ * 2)
  .take(5)
  .toList  // forces evaluation
println(result)  // List(4, 8, 12, 16, 20)
// view = lazy, no intermediate collections
// LazyList (Stream) for infinite sequences
val fibs: LazyList[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map(_ + _)
08

模式匹配

匹配 Case Class

Sealed trait + case class 构成代数数据类型(ADT)。编译器检查穷尽性——添加一个 case 会警告哪些匹配需要更新。模式匹配直接解构 case class。这是地道的 Scala 建模方式。

scala
sealed trait Shape
case class Circle(r: Double) extends Shape
case class Square(s: Double) extends Shape
case class Rect(w: Double, h: Double) extends Shape
def area(s: Shape): Double = s match {
  case Circle(r) => math.Pi * r * r
  case Square(s) => s * s
  case Rect(w, h) => w * h
}
println(area(Circle(5)))  // 78.53...

守卫与条件

守卫(if condition)为模式添加运行时检查。使匹配更具表达力。顺序很重要——第一个匹配胜出。使用守卫处理范围、条件或简单模式无法表达的复杂逻辑。

scala
val n = 15
val desc = n match {
  case x if x < 0 => "negative"
  case 0 => "zero"
  case x if x % 2 == 0 => "even"
  case _ => "odd"
}
println(desc)  // odd
// Guards add boolean conditions to patterns

匹配集合

:: cons 模式将列表解构为头部和尾部。_* 匹配数组/列表中零个或多个元素。这些模式支持递归列表处理和结构分解。对解析和树遍历非常强大。

scala
val list = List(1, 2, 3, 4)
list match {
  case Nil => println("empty")
  case head :: Nil => println(s"one: ${head}")
  case head :: tail => println(s"head=${head}, rest=${tail}")
  case _ => println("other")
}
// head :: tail destructures a list
val arr = Array(1, 2, 3)
arr match {
  case Array(1, _*) => println("starts with 1")
  case _ => println("other")
}

匹配 Option 与 Either

对 Option/Either 进行模式匹配是地道的写法——Some(x)/None、Right(x)/Left(e)。for 推导式脱糖为带 match 的 flatMap。这使得错误处理自然流动,无需显式 if-else 检查。

scala
def find(id: Int): Option[String] =
  if (id == 1) Some("Alice") else None
find(1) match {
  case Some(name) => println(s"Found: ${name}")
  case None => println("Not found")
}
// In for-comprehensions
val result = for {
  name <- find(1)
  upper = name.toUpperCase
} yield upper
println(result)  // Some(ALICE)

提取器(unapply)

通过 unapply 自定义提取器可对任何类型进行模式匹配。unapply 方法返回提取值的 Option。这让你可以定义自己的模式——对 DSL 和解析非常强大。Case class 自动生成 unapply。

scala
object Email {
  def unapply(s: String): Option[(String, String)] = {
    val parts = s.split("@")
    if (parts.length == 2) Some((parts(0), parts(1))) else None
  }
}
"[email protected]" match {
  case Email(user, domain) =>
    println(s"User: ${user}, Domain: ${domain}")
  case _ => println("Not an email")
}
09

泛型与隐式

泛型类与方法

泛型(类型参数 [A])编写类型安全、可复用的代码。使用 [A] 表示单个类型,[A, B] 表示两个。类型推断通常会推断出类型。泛型在运行时被擦除(JVM 限制)但在编译时检查。

scala
class Stack[A] {
  private var items: List[A] = Nil
  def push(x: A): Unit = { items = x :: items }
  def pop: Option[A] = items.headOption
}
val s = new Stack[Int]
s.push(1); s.push(2)
println(s.pop)  // Some(2)
def first[A](list: List[A]): Option[A] = list.headOption
println(first(List("a", "b")))  // Some(a)

类型边界

<: 上界(A 是子类型),>: 下界(A 是父类型)。上下文边界(A: Ordering)要求该类型的隐式值。视图边界(A <% B)已弃用——请使用上下文边界替代。这些约束类型参数。

scala
// Upper bound: A must be Animal or subclass
class Box[A <: Animal](val content: A)
// Lower bound: A must be Dog or superclass
class Kennel[A >: Dog](val occupant: A)
// Context bound: A must have an Ordering
def max[A: Ordering](a: A, b: A): A =
  if (implicitly[Ordering[A]].gt(a, b)) a else b
abstract class Animal { def name: String }
class Dog extends Animal { def name = "Rex" }

隐式参数

隐式参数由编译器从作用域中注入。用于配置、类型类或你不想到处传递的依赖。用 implicit val/def 声明。编译器搜索封闭作用域和伴生对象。

scala
def greet(name: String)(implicit greeting: String): String =
  s"${greeting}, ${name}!"
implicit val defaultGreeting: String = "Hello"
println(greet("Alice"))  // Hello, Alice! (implicit injected)
println(greet("Bob")("Hi"))  // Hi, Bob! (explicit override)
// Compiler finds implicit in scope

类型类(隐式转换)

类型类(通过隐式实现)在不修改类型的情况下为其添加行为——即特设多态。定义一个 trait,提供隐式实例,并使用隐式参数。这就是 Ordering、Numeric 和 Show 的工作方式。比继承更灵活。

scala
trait Show[A] { def show(a: A): String }
object Show {
  implicit val intShow: Show[Int] = (a: Int) => a.toString
  implicit val strShow: Show[String] = identity
}
def printIt[A](a: A)(implicit s: Show[A]): Unit =
  println(s.show(a))
printIt(42)       // 42
printIt("hello")  // hello
// Type class: ad-hoc polymorphism

扩展方法(Scala 2)

Implicit class 为现有类型添加扩展方法。定义带单个参数的 implicit class,其方法即可在该类型上使用。用于为 Int、String 等添加工具方法。Scala 3 使用更清晰的 'extension' 语法。

scala
implicit class IntOps(val n: Int) extends AnyVal {
  def times(f: => Unit): Unit = (1 to n).foreach(_ => f)
  def squared: Int = n * n
}
5.times { print("hi") }  // hihihihihi
println(5.squared)  // 25
// Scala 3: extension (n: Int) def squared = n * n
10

并发与 Future

Future 与异步

Future 表示异步计算。onComplete 注册回调。在生产 Web 服务器中永远不要阻塞(Await.result)——它会占用线程。使用 for 推导式以函数式方式链式调用 Future。需要 ExecutionContext。

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f: Future[Int] = Future {
  Thread.sleep(1000)
  42
}
f.onComplete {
  case scala.util.Success(v) => println(s"Got ${v}")
  case scala.util.Failure(e) => println(s"Err: ${e}")
}
// Don't block in production—use callbacks or for-comprehensions

组合 Future

对 Future 使用 for 推导式会顺序执行(每个等待前一个)。并行执行时,先启动所有 Future,再用 Future.sequence 组合。Future.traverse 一步完成映射和序列化。这是组合异步工作的地道方式。

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f1 = Future { 10 }
val f2 = Future { 20 }
val sum = for {
  a <- f1
  b <- f2
} yield a + b  // Future(30)
// Parallel execution
val results = Future.sequence(List(
  Future { 1 }, Future { 2 }, Future { 3 }
))
results.map(_.sum)  // Future(6)

并行集合

.par 将集合转换为并行版本——操作自动使用多线程。适合大型集合上的 CPU 密集型工作。注意:非结合性操作(如减法)可能产生不同结果。不适合 I/O 密集型工作。

scala
val nums = (1 to 1000000).toList
val sum = nums.par.sum  // parallel sum
println(sum)
val doubled = nums.par.map(_ * 2).toList
// .par converts to ParCollection
// Operations run on multiple threads
// Use for CPU-bound work on large collections

Promise(手动 Future)

Promise 是 Future 的可写端——你用 success/failure 手动完成它。用于将基于回调的 API 桥接到 Future,或需要从多处完成 Future 时。Future 是只读的;Promise 是一次写入的。

scala
import scala.concurrent.{Promise, Future, ExecutionContext}
import ExecutionContext.Implicits.global
val p = Promise[Int]()
val f = p.future
// Complete the promise from another thread
Future { Thread.sleep(100); p.success(42) }
f.foreach(println)  // 42 (when complete)
// p.failure(new Exception) for errors
// Promise = write side, Future = read side

同步与异步(Await)

Await.result 阻塞当前线程直到 Future 完成(带超时)。仅在测试或 main 方法中使用——在异步代码中阻塞违背了初衷。在生产中,使用回调(onComplete、map)或 for 推导式保持非阻塞。

scala
import scala.concurrent.{Future, Await}
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
val f = Future { Thread.sleep(500); 42 }
// Block and wait (use sparingly—mainly in tests)
val result = Await.result(f, 1.second)
println(result)  // 42
// Await.ready returns Try, Await.result returns value
// Avoid in production servers—use callbacks instead
11

隐式深入

隐式参数

当作用域中存在匹配类型的隐式值时,编译器会自动传递隐式参数。这减少了'上下文'参数(ExecutionContext、日志、配置)的样板代码。编译器搜索:局部作用域、伴生对象、隐式作用域。你总是可以显式传递以覆盖。过度使用会使代码难以追踪——用于真正上下文相关的依赖。

scala
import scala.concurrent.ExecutionContext

// Method with implicit parameter
def process[A](data: List[A])
    (implicit ec: ExecutionContext): Future[Unit] = {
  Future { data.foreach(println) }
}

// The compiler finds an implicit ExecutionContext in scope
implicit val ec: ExecutionContext = ExecutionContext.global
process(List(1, 2, 3))  // ec passed automatically

// Explicitly providing (overrides implicit)
process(List(1, 2, 3))(myCustomEC)

// Multiple implicit parameters
def log(msg: String)(implicit
    level: Level, logger: Logger): Unit = {
  logger.log(level, msg)
}

隐式转换

隐式转换在需要时自动在类型之间转换。implicit class(继承 AnyVal 以实现零开销)为现有类型添加扩展方法——这就是 Scala 为 Int、String 等添加方法的方式。注意:隐式转换可能使代码令人困惑(什么正在被转换?)。对于扩展,优先使用 implicit class 而非原始的 implicit def。使用 import scala.language.implicitConversions 启用。

scala
import scala.language.implicitConversions

// Implicit conversion: one type to another
implicit def intToString(n: Int): String = n.toString
val s: String = 42  // intToString(42) called implicitly

// Extension via implicit class (Scala 2.10+)
implicit class RichInt(val self: Int) extends AnyVal {
  def times(f: => Unit): Unit = (1 to self).foreach(_ => f)
  def squared: Int = self * self
}

5.times { println("hi") }  // prints hi 5 times
3.squared  // 9

// Implicit conversion for type compatibility
implicit def javaToScalaList(jl: java.util.List[Int]): List[Int] =
  import scala.jdk.CollectionConverters._
  jl.asScala.toList

val javaList: java.util.List[Int] = ???
val scalaList: List[Int] = javaList  // converted

隐式解析优先级

编译器按优先级解析隐式:局部作用域 > 伴生对象 > 导入 > 继承。如果两个同类型的隐式在作用域中同等存在,会得到 'ambiguous implicit' 错误。LowPriorityImplicits trait 模式提供可被更特定隐式覆盖的默认值。理解解析顺序对库设计至关重要——将默认值放在低优先级 trait 中,以便用户可以覆盖。

scala
// Priority of implicit resolution (highest to lowest):
// 1. Local implicit (defined in current scope)
implicit val ec1: ExecutionContext = ec1

// 2. Implicit in companion object
object MyService {
  implicit val ec2: ExecutionContext = ec2  // lower priority
}

// 3. Implicit scope (imported)
import somePackage.Implicits._

// 4. Implicit parameter default (inherited trait)
trait DefaultEc {
  implicit val ec: ExecutionContext = ExecutionContext.global
}

// More specific type wins
implicit def ord1: Ordering[Int] = ???
implicit def ord2: Ordering[Int] = ???  // ambiguous error!

// LowPriorityImplicits trait pattern
object MyLib {
  implicit val high: Ordering[Int] = ???
}
object MyLib extends LowPriorityImplicits
trait LowPriorityImplicits {
  implicit val low: Ordering[Int] = ???  // fallback
}

上下文边界与证据

上下文边界 [A: TypeClass] 是隐式参数的语法糖——它们断言存在隐式的 TypeClass[A]。使用 implicitly[TypeClass[A]](Scala 2)或 summon[TypeClass[A]](Scala 3)检索它。上下文边界使类型类约束可读:def sort[A: Ordering]。多个边界可叠加:[A: Ordering: Numeric]。这是表达类型类要求的地道方式。

scala
// Context bound: [A: Ordering] means there's an implicit Ordering[A]
def max[A: Ordering](a: A, b: A): A = {
  val ord = implicitly[Ordering[A]]  // retrieve the implicit
  if (ord.gt(a, b)) a else b
}

// Equivalent to:
def max2[A](a: A, b: A)(implicit ord: Ordering[A]): A =
  if (ord.gt(a, b)) a else b

// summon (Scala 3) instead of implicitly
def max3[A: Ordering](a: A, b: A): A = {
  val ord = summon[Ordering[A]]
  if (ord.gt(a, b)) a else b
}

// Type class evidence
def sort[A: Ordering](list: List[A]): List[A] =
  list.sorted  // uses the implicit Ordering

// Multiple context bounds
def process[A: Ordering: Numeric](x: A, y: A): A = ???

隐式作用域与伴生对象

隐式作用域比当前作用域更广——它包括相关类型的伴生对象。这就是为什么你不需要导入 Ordering[Int]:它位于 Int 的伴生对象中。这种机制使类型类易用:在类型的伴生对象中定义实例,它会自动可用。包对象持有包的共享隐式。这种设计实现了'零导入'的类型类使用。

scala
// Implicit in companion object is found automatically
case class UserId(value: Long)
object UserId {
  implicit val ordering: Ordering[UserId] =
    Ordering.by(_.value)
}

// No import needed—companion object implicits are in scope
List(UserId(3), UserId(1), UserId(2)).sorted
// Works because Ordering[UserId] is in UserId's companion

// Implicit scope includes:
// 1. Companion object of the type (UserId)
// 2. Companion object of the type class (Ordering)
// 3. Companion objects of type parameters

// This is why Int has an Ordering:
// object Int { implicit val ord: Ordering[Int] = ... }

// Package object for shared implicits
package object myapp {
  implicit val ec: ExecutionContext = ExecutionContext.global
  type Id = Long
}
12

类型类

定义类型类

类型类是由类型参数化的 trait,其实例为特定类型提供行为。与继承不同,你可以追溯地添加类型类实例(即使你不拥有该类型)。Show[A] 定义如何显示 A。实例位于伴生对象中(自动隐式作用域)。这是特设多态——不修改类型即可为不同类型提供不同行为。类型类是 Scala 最强大的抽象。

scala
// Type class: a trait parameterized by type
trait Show[A] {
  def show(a: A): String
}

// Instances for specific types
object Show {
  // Instance for Int
  implicit val showInt: Show[Int] = (a: Int) => a.toString

  // Instance for String
  implicit val showString: Show[String] = (s: String) => s""$s""

  // Instance for List (recursive)
  implicit def showList[A](implicit s: Show[A]): Show[List[A]] =
    (list: List[A]) => list.map(s.show).mkString("[", ", ", "]")
}

// Usage with implicit parameter
def print[A](a: A)(implicit s: Show[A]): Unit =
  println(s.show(a))

print(42)           // 42
print("hello")      // "hello"
print(List(1, 2, 3))  // [1, 2, 3]

使用类型类(语法糖)

上下文边界 [A: Show] + summon 检索类型类实例。扩展方法(implicit class)添加如 .show 等使用类型类的方法。这种组合提供了简洁的 API:如果存在 Show[Int],42.show 即可工作。标准库提供了许多类型类:Numeric、Ordering、Eq、Monoid(Cats)。导入语法(Numeric.Implicits._)添加如 + 和 sum 等使用类型类的运算符。

scala
// Context bound syntax
def printAll[A: Show](items: List[A]): Unit =
  items.foreach(a => println(summon[Show[A]].show(a)))

// Extension methods via implicit class
implicit class ShowOps[A](val a: A) extends AnyVal {
  def show(implicit s: Show[A]): String = s.show(a)
}

42.show           // "42"
"hi".show         // ""hi""
List(1,2).show    // "[1, 2]"

// Combining: type class + extension methods
def format[A: Show](a: A): String = a.show

// Standard library type classes
def sum[A: Numeric](xs: List[A]): A =
  summon[Numeric[A]].plus(xs.head, xs.tail.foldLeft(
    summon[Numeric[A]].zero)(summon[Numeric[A]].plus))

// Or with syntax:
import Numeric.Implicits._
def sum2[A: Numeric](xs: List[A]): A = xs.sum

常见类型类(Cats/Scalaz)

Cats 和 Scalaz 提供标准类型类。Monoid(empty + combine)实现通用聚合。Functor(map)和 Monad(pure + flatMap)对容器进行抽象(List、Option、Future、IO)。Eq 提供类型安全的相等性(避免意外的跨类型比较)。它们可组合:Monad 是 Functor,Monoid 是 Semigroup。类型类支持编写跨多种类型工作的通用、可复用代码。

scala
// Monoid: combine values with empty
trait Monoid[A] {
  def empty: A
  def combine(a: A, b: A): A
}
object Monoid {
  implicit val intAdd: Monoid[Int] = new Monoid[Int] {
    def empty = 0
    def combine(a: Int, b: Int) = a + b
  }
  implicit def listMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
    def empty = Nil
    def combine(a: List[A], b: List[A]) = a ++ b
  }
}

// Functor: map over structure
trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

// Monad: chain operations
trait Monad[F[_]] {
  def pure[A](a: A): F[A]
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}

// Eq: type-safe equality
trait Eq[A] {
  def eqv(a: A, b: A): Boolean
}

// Semigroup: combine (no empty)
trait Semigroup[A] {
  def combine(a: A, b: A): A
}

定律与测试类型类

类型类定律是实例必须满足的数学属性。Monoid 要求结合律和单位元。Functor 要求保持恒等和组合。像 Cats 这样的库提供定律定义;discipline + ScalaCheck 自动测试它们。定律是类型类强大的原因:通用代码(如 foldMap)对任何遵守定律的实例都能正确工作。始终验证你的实例满足定律——实例中的 bug 会破坏所有使用它们的通用代码。

scala
// Type class laws: properties that must hold
// Monoid laws:
// 1. Left identity:  combine(empty, a) == a
// 2. Right identity: combine(a, empty) == a
// 3. Associativity:  combine(a, combine(b, c)) == combine(combine(a, b), c)

// Functor laws:
// 1. Identity:    map(fa)(identity) == fa
// 2. Composition: map(fa)(f andThen g) == map(map(fa)(f))(g)

// Testing laws with ScalaCheck (discipline)
import org.scalacheck.Prop.forAll
import cats.kernel.laws.MonoidLaws

class MonoidSpec extends munit.FunSuite with Discipline {
  checkAll("Int Monoid", MonoidLaws[Int].monoid)
}

// Custom law test
def monoidLeftIdentity[A](implicit m: Monoid[A], arb: Arbitrary[A]) =
  forAll { (a: A) =>
    m.combine(m.empty, a) == a
  }

// Laws make type classes trustworthy:
// if an instance satisfies laws, generic code works correctly

类型类派生(Scala 3)

Scala 3 通过 'derives' 关键字和 Mirror 简化了类型类派生。编译器可以通过组合元素实例为 case class 和 enum 自动生成实例。这消除了为每个 case class 编写实例的样板代码(在 Scala 2 中常用 shapeless)。Cats 和 Circe 等库支持 Scala 3 派生。Mirror 类型提供对类型结构(字段类型、标签)的编译时访问,用于泛型编程。

scala
// Scala 3: derive type class instances automatically
import scala.deriving.Mirror

trait Show[A] {
  def show(a: A): String
}

object Show {
  // Inline given for derivation
  given showInt: Show[Int] with
    def show(a: Int) = a.toString

  given showString: Show[String] with
    def show(s: String) = s""$s""

  // Derive for products (case classes)
  given showProduct[A](using m: Mirror.ProductOf[A])
      (using ev: Show[m.MirroredElemTypes]): Show[A] with
    def show(a: A): String = ???

  // Or use Scala 3's derivation
  inline given derive[A](using m: Mirror.Of[A]): Show[A] = ???
}

// Auto-derive for case classes
case class Person(name: String, age: Int) derives Show
// Show[Person] is generated automatically
13

For 推导式深入

基础 For 推导式

For 推导式是 flatMap/map/withFilter 的语法糖。每个 <- 是 flatMap(最后一个除外,它是 map)。if 守卫变为 withFilter。yield 使其返回集合;省略 yield 使其变为命令式(foreach)。这适用于任何具有 flatMap/map 的类型(Monad):List、Option、Future、Try、IO。掌握 for 推导式是地道 Scala 的关键——它们用可读的顺序语法替代嵌套的 map/flatMap。

scala
// For comprehension: syntactic sugar for flatMap/map
val result = for {
  x <- List(1, 2, 3)
  y <- List(10, 20)
} yield x + y
// List(11, 21, 12, 22, 13, 23)

// Desugared:
List(1, 2, 3).flatMap { x =>
  List(10, 20).map { y => x + y }
}

// With filters (if guards)
val evens = for {
  x <- 1 to 10
  if x % 2 == 0
} yield x
// Vector(2, 4, 6, 8, 10)

// Desugared:
(1 to 10).withFilter(_ % 2 == 0).map(identity)

// Without yield: imperative (foreach)
for (x <- 1 to 3) println(x)  // 1, 2, 3

For 与 Option 和 Future

For 推导式适用于任何 Monad。对 Option,它们在 None 时短路(返回 None)。对 Future,它们在失败时短路。这使得顺序异步/可能失败的代码读起来像直线命令式代码,同时保持函数式。每个 <- 行可以依赖前一个绑定。这比嵌套的 flatMap 调用干净得多。相同的语法适用于 Try、Either、IO 和自定义 monad。

scala
// Option: chain operations that might return None
def getUser(id: Int): Option[User] = ???
def getEmail(user: User): Option[String] = ???

val email: Option[String] = for {
  user <- getUser(42)
  email <- getEmail(user)
} yield email

// Desugared:
getUser(42).flatMap(user => getEmail(user).map(email => email))

// Future: chain async operations
val result: Future[Int] = for {
  user <- fetchUser(1)      // Future[User]
  posts <- fetchPosts(user) // Future[List[Post]]
} yield posts.size

// If any returns None/failed Future, the whole chain short-circuits
// This is the power of monadic composition

For 与 Either 及错误处理

Either 是 Scala 的类型化错误处理。For 推导式链式调用 Either,在 Left(错误)时短路。这是函数式错误处理——没有异常,错误是值。Left 类型是错误(通常是 String 或 sealed trait)。Either 在 Scala 2.12+ 中是右偏的(map/flatMap 作用于 Right)。这种模式用可组合、类型安全的错误传播替代 try/catch。Cats 的 Validated 是累积错误的替代方案。

scala
// Either for error handling
def parseAge(s: String): Either[String, Int] =
  s.toIntOption.toRight(s"not a number: $s")

def validateAge(age: Int): Either[String, Int] =
  if (age >= 0) Right(age) else Left(s"negative: $age")

val result: Either[String, Int] = for {
  age <- parseAge("30")     // Right(30)
  valid <- validateAge(age) // Right(30)
} yield valid

// With error in chain
val error: Either[String, Int] = for {
  age <- parseAge("abc")    // Left("not a number: abc")
  valid <- validateAge(age) // skipped!
} yield valid
// error == Left("not a number: abc")

// Scala 3: for-comprehensions work with Either directly
// (Scala 2 needed either.map(_.right) or withFilter)

脱糖与自定义 Monad

任何具有 flatMap 和 map 的类型都支持 for 推导式——这就是 Monad 模式。在你的类型上定义这些方法以启用 for 语法。=(非 <-)创建局部绑定(不脱糖为 flatMap)。理解脱糖有助于调试复杂的推导式和实现自定义 monad。编译器将 for 推导式转换为 flatMap/map/withFilter 链。这就是为什么 for 能在 List、Option、Future、IO 等上统一工作。

scala
// For-comprehensions require flatMap, map, withFilter
// Define your own monad to use for-comprehensions

case class Box[A](value: A) {
  def map[B](f: A => B): Box[B] = Box(f(value))
  def flatMap[B](f: A => Box[B]): Box[B] = f(value)
  def withFilter(p: A => Boolean): Box[A] =
    if (p(value)) this else throw new NoSuchElementException
}

val result = for {
  x <- Box(10)
  y <- Box(20)
  if x < y
} yield x + y
// Box(30)

// Desugaring steps:
// 1. Box(10).flatMap { x =>
// 2.   Box(20).withFilter(_ > x... wait, order matters
// Actual:
// Box(10).flatMap(x =>
//   Box(20).withFilter(y => x < y).map(y => x + y))

// Assignment within for (= instead of <-)
for {
  x <- List(1, 2, 3)
  doubled = x * 2  // local val
} yield doubled

For 与 Map/FlatMap(何时使用)

对 2 个或更多依赖操作使用 for 推导式——它们比嵌套的 flatMap 更易读。对单个转换,map 更清晰。对扁平化,直接用 flatMap。对副作用(无结果),使用不带 yield 的 for。当每步依赖前一步(monadic 链式)时,for 推导式大放异彩。它们使异步/错误处理代码读起来是顺序的。避免深度嵌套的 for(>5 层)——提取辅助函数以提升可读性。

scala
// For: sequential, dependent operations
val result = for {
  user <- fetchUser(id)
  profile <- fetchProfile(user.id)
  avatar <- fetchAvatar(profile.avatarId)
} yield avatar

// Equivalent with flatMap (harder to read):
fetchUser(id).flatMap(user =>
  fetchProfile(user.id).flatMap(profile =>
    fetchAvatar(profile.avatarId)))

// Map: single transformation (no chaining)
users.map(_.name)  // simple, use map

// FlatMap: when you need to chain but for is overkill
users.flatMap(_.posts)  // List[Post]

// For without yield: side effects
for (user <- users) {
  saveToDb(user)
  sendEmail(user)
}

// Guidelines:
// - 1 operation: use map/flatMap directly
// - 2+ dependent operations: use for-comprehension
// - Side effects: use for without yield
14

Scala 3:Given 与 Using

Given 实例(替代 implicit val)

Scala 3 用 'given' 替代 implicit val/def。given 更清晰、更明确。匿名 given(given Type = ...)有编译器生成的名称。'given T with' 定义带多个方法的实例。条件 given(given [A: Ordering]: Ordering[List[A]])替代 implicit def。伴生对象中的 given 自动在隐式作用域中,就像 Scala 2 一样。关键字变更减少了 'implicit' 的重载(在 Scala 2 中它有 4 种含义)。

scala
// Scala 2: implicit val
// implicit val ec: ExecutionContext = ExecutionContext.global

// Scala 3: given
given ec: ExecutionContext = ExecutionContext.global

// Anonymous given (inferred name)
given ExecutionContext = ExecutionContext.global

// Given with 'with' for complex types
given Show[Int] with
  def show(a: Int): String = a.toString

// Given in companion object (automatic scope)
object MyType:
  given Ordering[MyType] = Ordering.by(_.id)

// Conditional given (like implicit def)
given [A: Ordering]: Ordering[List[A]] with
  def compare(a: List[A], b: List[A]): Int =
    a.zip(b).find((x, y) => x != y) match
      case Some((x, y)) => summon[Ordering[A]].compare(x, y)
      case None => a.length - b.length

Using 子句(替代隐式参数)

Scala 3 用 'using' 替代隐式参数。这分离了 'implicit' 的两种含义(参数 vs 转换)。using 子句用 'using' 关键字显式传递。上下文边界 [A: T] 保持不变。summon[T] 替代 implicitly[T](名称更清晰)。using 关键字使调用点在提供上下文时一目了然。这是纯粹的语法变更——语义与隐式参数相同。

scala
// Scala 2: implicit parameter
// def process[A](data: List[A])(implicit ec: ExecutionContext): Unit

// Scala 3: using
def process[A](data: List[A])(using ec: ExecutionContext): Unit =
  data.foreach(println)

// Context bound (unchanged)
def sort[A: Ordering](list: List[A]): List[A] = list.sorted

// Multiple using clauses
def log(msg: String)(using level: Level, logger: Logger): Unit =
  logger.log(level, msg)

// summon replaces implicitly
def max[A: Ordering](a: A, b: A): A =
  val ord = summon[Ordering[A]]
  if ord.gt(a, b) then a else b

// Provide explicitly with 'using'
process(data)(using myEC)
log("hello")(using Level.INFO, myLogger)

扩展方法(Scala 3)

Scala 3 用 'extension' 替代 implicit class——更清晰、更专注。extension (self: T) 在 T 上定义方法。扩展可以是泛型的(extension [A])。与 using 结合,它们提供类型类语法(42.show)。扩展只是方法添加——它们不创建包装对象(在 Scala 2 中通过 AnyVal 实现零开销,在 Scala 3 中原生支持)。这是 Scala 3 中为现有类型添加方法的地道方式。

scala
// Scala 2: implicit class
// implicit class RichInt(val self: Int) extends AnyVal {
//   def squared: Int = self * self
// }

// Scala 3: extension
extension (self: Int)
  def squared: Int = self * self
  def times(f: => Unit): Unit = (1 to self).foreach(_ => f)

5.squared   // 25
3.times { println("hi") }

// Extension on generic type
extension [A](self: List[A])
  def takeWhileInclusive(p: A => Boolean): List[A] = ???

// Extension with using (type class syntax)
extension [A](self: A) def show(using s: Show[A]): String = s.show(self)

42.show  // uses given Show[Int]

// Multiple extensions in one block
extension (s: String)
  def isBlank: Boolean = s.trim.isEmpty
  def words: List[String] = s.split(" ").toList

Enum 与 ADT(Scala 3)

Scala 3 enum 替代 sealed trait + case object 用于 ADT。它们更简洁,支持参数、字段和方法。Enum case 可以有参数(类似 case class)。模式匹配有穷尽性检查。Enum 可以是泛型的(Option[A])。这统一了 enum 和 ADT 为一个构造。对于开放层次结构(可扩展),仍使用 sealed trait + case class。对于封闭枚举/ADT,enum 更干净。

scala
// Scala 3 enum (replaces sealed trait + case objects)
enum Color:
  case Red, Green, Blue

enum HttpStatus(val code: Int):
  case Ok extends HttpStatus(200)
  case NotFound extends HttpStatus(404)
  case Error extends HttpStatus(500)

// Pattern matching (exhaustive)
def describe(c: Color): String = c match
  case Color.Red => "red"
  case Color.Green => "green"
  case Color.Blue => "blue"

// Access fields
HttpStatus.Ok.code  // 200

// Parameterized enum cases
enum Option[+A]:
  case Some(value: A)
  case None

// ADT with methods
enum Tree[+A]:
  case Leaf(value: A)
  case Node(left: Tree[A], right: Tree[A])

  def size: Int = this match
    case Leaf(_) => 1
    case Node(l, r) => l.size + r.size + 1

顶层定义与缩进

Scala 3 允许顶层定义——无需将所有内容包装在 object 中。这简化了文件结构(类似 Python/Go)。新语法同时支持花括号和显著缩进(可选)。if/then 替代带括号的 if/else。match 可作为不带花括号的表达式使用。这些变更使 Scala 3 更易上手,同时保持向后兼容。你可以混合风格——在增加清晰度处使用花括号,在减少噪音处使用缩进。

scala
// Scala 3: top-level definitions (no class wrapper needed)
// File: MyMath.scala
def add(a: Int, b: Int): Int = a + b  // top-level function

val Pi: Double = 3.14159  // top-level val

type Id = Long  // top-level type alias

given Show[Int] = (a: Int) => a.toString  // top-level given

extension (i: Int) def squared: Int = i * i  // top-level extension

// Indentation-based syntax (optional, braces still work)
def factorial(n: Int): Int =
  if n <= 1 then 1
  else n * factorial(n - 1)

// Or with braces:
def factorial2(n: Int): Int = {
  if (n <= 1) 1
  else n * factorial2(n - 1)
}

// if/then, match/case without braces
val sign = if x > 0 then 1 else -1
val desc = x match
  case 0 => "zero"
  case _ => "nonzero"
15

扩展方法与语法

Implicit Class(Scala 2)

implicit class(Scala 2.10+)为现有类型添加扩展方法。继承 AnyVal 使其零分配(编译器擦除包装)。该类必须接受单个构造器参数(被扩展的类型)。implicit class 上的方法在被扩展类型上可用。这就是 Scala 丰富 Int、String 等的方式。将 implicit class 放在包对象或工具对象中以共享。在 Scala 3 中,使用 'extension' 替代。

scala
// Scala 2: implicit class for extension methods
import scala.language.implicitConversions

implicit class RichString(val s: String) extends AnyVal {
  def wordCount: Int = s.split("\\s+").length
  def slug: String = s.toLowerCase.replaceAll("[^a-z0-9]+", "-")
  def encrypt(key: Int): String = s.map(c => (c + key).toChar)
}

"Hello World".wordCount  // 2
"My Blog Post!".slug     // "my-blog-post-"
"abc".encrypt(1)         // "bcd"

// AnyVal avoids allocation (zero overhead)
// implicit class must be in a trait, class, or object

// For generic extensions
implicit class RichList[A](val list: List[A]) extends AnyVal {
  def middle: Option[A] = list.lift(list.length / 2)
}

List(1, 2, 3, 4, 5).middle  // Some(3)

扩展方法实践

扩展方法是向集合和其他类型添加工具的标准方式。distinctBy、chunked、tap 是常见的添加。=== 和 =!= 运算符(通过 Eq 类型类)提供类型安全的相等性(不同于允许跨类型的 ==)。将扩展分组在一个对象中,按需导入。这保持了核心类型的干净,同时允许领域特定方法。Scala 的集合库本身就广泛使用这种模式。

scala
// Common pattern: add utility methods to collections
object CollectionExtensions {
  implicit class RichSeq[A](val seq: Seq[A]) extends AnyVal {
    def chunked(size: Int): Seq[Seq[A]] =
      seq.grouped(size).toSeq

    def distinctBy[B](f: A => B): Seq[A] =
      seq.groupBy(f).values.map(_.head).toSeq

    def tap(f: A => Unit): Seq[A] = {
      seq.foreach(f)
      seq
    }
  }
}

import CollectionExtensions._

List(1, 2, 3, 4, 5).chunked(2)  // List(List(1,2), List(3,4), List(5))
List("aa", "bb", "ab").distinctBy(_.head)  // List("aa", "ab")
List(1, 2, 3).tap(println)  // prints 1,2,3, returns List(1,2,3)

// Type class syntax via extension
implicit class EqOps[A](val a: A) extends AnyVal {
  def ===(b: A)(implicit eq: Eq[A]): Boolean = eq.eqv(a, b)
  def =!=(b: A)(implicit eq: Eq[A]): Boolean = !eq.eqv(a, b)
}

隐式转换(谨慎使用)

隐式转换自动转换类型,但它们很危险——代码行为变得不明显。Scala 2.10+ 要求显式选择启用(scala.language.implicitConversions)。优先使用扩展方法(添加方法而不改变类型)而非转换(改变类型)。合法用途:Java 互操作(在集合类型之间转换)、DSL 构建。编译器会警告隐式转换——认真对待这些警告。在 Scala 3 中,given Conversion[T, U] 是显式机制。

scala
import scala.language.implicitConversions

// Implicit conversion between types
implicit def stringToInt(s: String): Int = s.toInt
val n: Int = "42"  // stringToInt("42")

// Dangerous: can cause surprising behavior
implicit def intToBoolean(n: Int): Boolean = n != 0
if (1) println("yes")  // works! (intToBoolean(1))

// Safer: use extension methods instead
extension (n: Int) def toBool: Boolean = n != 0
if (1.toBool) println("yes")

// When implicit conversions are appropriate:
// 1. Java interop (java.util.List <-> scala.List)
implicit def javaListToScala[A](jl: java.util.List[A]): List[A] =
  import scala.jdk.CollectionConverters._
  jl.asScala.toList

// 2. Backward compatibility layers
// 3. DSL construction (use sparingly)

// Enable per-file: import scala.language.implicitConversions

类型级编程

类型级编程将信息编码在类型中,由编译器检查。幻影类型(未使用的类型参数)跟踪状态(Open/Closed)防止误用(不能读取已关闭的文件)。Peano 自然数将数字表示为类型。这实现了编译时正确性保证——bug 变为编译错误。用于状态机、度量单位、定长向量。强大但复杂——当安全性值得类型复杂性时使用。像 shapeless(Scala 2)这样的库支持高级类型级编程。

scala
// Phantom types: track state at type level
sealed trait State
trait Open extends State
trait Closed extends State

class File[S <: State] private (val path: String)

object File {
  def open(path: String): File[Open] = new File[Open](path)
}

def close[S <: Open](f: File[S]): File[Closed] =
  new File[Closed](f.path)  // unsafe cast internally

def read[S <: Open](f: File[S]): String = "data"
// def read[S <: Closed](f: File[S]): String  // compile error

val f = File.open("test.txt")
read(f)  // OK: f is File[Open]
val closed = close(f)
// read(closed)  // COMPILE ERROR: closed is File[Closed]

// Type-level natural numbers (Peano)
sealed trait Nat
trait Zero extends Nat
trait Succ[N <: Nat] extends Nat

// Compile-time list length check
type _0 = Zero
type _1 = Succ[_0]
type _2 = Succ[_1]

Opaque 类型(Scala 3)

Opaque 类型(Scala 3)创建零开销的 newtype——不同于值类(AnyVal),它们从不装箱,在运行时确实只是底层类型。类型区分仅在编译时存在,防止混淆(Celsius vs Fahrenheit、UserId vs Long)。在定义对象内部,类型及其底层类型可互换;在外部,它们是不同的。这是来自 Haskell 的 'newtype' 模式——无运行时成本的领域驱动类型。用于 ID、单位和领域原语。

scala
// Scala 2: value classes for zero-overhead wrappers
// case class UserId(value: Long) extends AnyVal

// Scala 3: opaque types (true zero overhead, no boxing)
object Types:
  type UserId = Long
  object UserId:
    def apply(value: Long): UserId = value

  type Email = String
  object Email:
    def apply(s: String): Email =
      require(s.contains("@"), "invalid email")
      s

import Types.*
val id: UserId = UserId(42)  // just a Long at runtime
val email: Email = Email("[email protected]")

// UserId and Long are NOT interchangeable outside the object
// def wrong(x: Long): UserId = x  // ERROR
// But inside the object, they're the same

// Newtype pattern: domain types without overhead
type Celsius = Double
type Fahrenheit = Double
// Prevents mixing up Celsius and Fahrenheit
def toF(c: Celsius): Fahrenheit = c * 9 / 5 + 32
16

测试(ScalaTest 与 ScalaCheck)

ScalaTest 风格

ScalaTest 提供多种风格。FunSuite 最简单(test("name") { ... })。FlatSpec 是 BDD 风格("A Stack" should "...")。Matchers 提供可读的断言(shouldBe、should contain、should throw)。每个项目选择一种风格以保持一致。FunSuite 适合单元测试;FlatSpec 适合行为聚焦的测试。所有风格支持相同的匹配器和生命周期钩子。风格影响语法,不影响能力。

scala
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

// FunSuite: simple test functions
class MyTest extends AnyFunSuite with Matchers {
  test("addition works") {
    1 + 1 should be (2)
    1 + 1 shouldBe 2
    List(1, 2, 3) should contain (2)
  }

  test("string operations") {
    "hello".length shouldBe 5
    "hello" should startWith ("he")
  }
}

// FlatSpec: BDD-style
import org.scalatest.flatspec.AnyFlatSpec
class StackSpec extends AnyFlatSpec with Matchers {
  "A Stack" should "pop values in LIFO order" in {
    val stack = Stack(1, 2, 3)
    stack.pop() shouldBe 3
  }

  it should "throw on empty pop" in {
    val stack = Stack()
    a [NoSuchElementException] shouldBe thrownBy(stack.pop())
  }
}

// WordSpec, FreeSpec, PropSpec also available

断言与匹配器

Matchers 提供富有表现力的断言。shouldBe/should be 用于相等性。contain、have size、have key 用于集合。startWith/endWith/include 用于字符串。thrownBy 用于异常。自定义匹配器(be >、be <=)用于比较。'should' DSL 读起来像英语,使测试自文档化。对于复杂断言,使用自定义匹配器或简单的 assert(condition)。避免过度链式匹配器——可读性优先于巧妙性。

scala
import org.scalatest.matchers.should.Matchers._

// Equality
result shouldBe 42
result should be (42)
result should equal (42)

// Collections
list should contain (5)
list should not contain (0)
list shouldBe empty
list should have size 5
list should contain inOrder (1, 2, 3)
map should contain key ("name")
map should contain value ("Alice")

// Strings
s should startWith ("Hello")
s should endWith ("world")
s should include ("lo")
s should fullyMatch regex "H.*d".r

// Exceptions
a [IOException] should be thrownBy riskyOp()
the [IOException] thrownBy riskyOp() should have message "fail"

// Custom matchers
result should be > 0
result should be <= 100

// Type checks
result shouldBe a [List[_]]
result should be an [IllegalArgumentException]

BeforeAndAfter 与夹具

BeforeAndAfterEach 在每个测试前后运行设置/拆卸。重写 beforeEach/afterEach。对于资源管理,loan 模式(withDb { conn => ... })更干净——它通过 try/finally 确保清理并使资源显式。ScalaTest 还支持夹具上下文(FixtureContext)和通过 trait 共享夹具。优先使用 loan 模式或夹具方法而非可变的 beforeEach 状态——它更函数式,避免了测试间的共享状态 bug。

scala
import org.scalatest.BeforeAndAfterEach
import org.scalatest.funsuite.AnyFunSuite

class DbTest extends AnyFunSuite with BeforeAndAfterEach {
  var conn: Connection = _

  override def beforeEach(): Unit = {
    conn = DriverManager.getConnection("jdbc:h2:mem:test")
    conn.execute("CREATE TABLE users (id INT, name VARCHAR)")
  }

  override def afterEach(): Unit = {
    conn.close()
  }

  test("insert works") {
    conn.execute("INSERT INTO users VALUES (1, 'Alice')")
    val count = conn.query("SELECT COUNT(*) FROM users")
    count shouldBe 1
  }
}

// Fixture via loan pattern
class FixtureTest extends AnyFunSuite {
  def withDb(test: Connection => Unit): Unit = {
    val conn = DriverManager.getConnection("jdbc:h2:mem:test")
    try test(conn) finally conn.close()
  }

  test("query works") {
    withDb { conn =>
      conn.execute("INSERT ...")
      // assertions
    }
  }
}

基于属性的测试(ScalaCheck)

ScalaCheck 生成随机测试输入,在许多用例上测试属性。forAll 默认用 100 个随机输入运行属性。whenever 过滤输入。自定义 Gen 类型约束生成的数据(Gen.choose、Gen.nonEmptyListOf)。这能捕获基于示例的测试会遗漏的边缘情况(空列表、负数、大值)。表驱动测试(Table)用于特定用例。基于属性的测试对纯函数和数据转换非常强大——定义什么应始终为真。

scala
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import org.scalacheck.Prop.forAll

class ListSpec extends AnyFunSuite with ScalaCheckPropertyChecks {
  // Property: reversing twice = identity
  test("reverse twice is identity") {
    forAll { (xs: List[Int]) =>
      xs.reverse.reverse shouldBe xs
    }
  }

  // Property with conditions
  test("head of sorted list is min") {
    forAll { (xs: List[Int]) =>
      whenever(xs.nonEmpty) {
        xs.sorted.head shouldBe xs.min
      }
    }
  }

  // Custom generators
  import org.scalacheck.Gen
  val smallInt = Gen.choose(1, 100)
  val nonEmptyList = Gen.nonEmptyListOf(smallInt)

  test("custom generator") {
    forAll(nonEmptyList) { (xs: List[Int]) =>
      xs should not be empty
      xs.forall(_ >= 1) shouldBe true
    }
  }

  // Table-driven tests
  test("addition table") {
    val cases = Table(
      ("a", "b", "sum"),
      (1, 2, 3),
      (10, 20, 30),
      (-1, 1, 0)
    )
    forAll(cases) { (a, b, sum) =>
      a + b shouldBe sum
    }
  }
}

模拟与测试替身

Mockito(通过 ScalaTestPlus)创建测试替身。mock[T] 创建模拟;when(...).thenReturn(...) 桩方法;verify 检查交互。使用模拟将被测单元与依赖(数据库、API)隔离。参数匹配器(argThat)验证特定参数。不要过度模拟——如果你模拟了一切,改为测试真实集成。对于复杂依赖,优先使用假对象(内存实现)而非模拟——它们更健壮、更易读。

scala
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito._

class UserServiceSpec extends AnyFunSuite with MockitoSugar {
  test("getUser returns user from repo") {
    // Create mock
    val repo = mock[UserRepository]
    val user = User(1, "Alice")

    // Stub: when X then Y
    when(repo.findById(1)).thenReturn(Some(user))

    val service = new UserService(repo)
    val result = service.getUser(1)

    result shouldBe Some(user)

    // Verify: was X called?
    verify(repo).findById(1)
    verify(repo, never()).findById(2)
  }

  test("createUser saves to repo") {
    val repo = mock[UserRepository]
    val service = new UserService(repo)

    service.createUser("Bob")

    // Verify with argument matcher
    verify(repo).save(argThat((u: User) => u.name == "Bob"))
  }
}

// Stubbing exceptions
when(repo.findById(99)).thenThrow(new RuntimeException("not found"))
17

高阶函数与函数式编程

函数作为值

在 Scala 中函数是值——你可以存储、传递和返回它们。(A, B) => C 是函数类型。Eta 展开(multiply _)将方法转换为函数值。高阶函数(接收/返回函数)实现强大的抽象:map、filter、reduce 都是 HOF。函数组合(andThen、compose)构建管道。柯里化(adder(5) 返回一个函数)是部分应用。这是 Scala 函数式编程的基础。

scala
// Functions are first-class values
val add: (Int, Int) => Int = (a, b) => a + b
val square: Int => Int = x => x * x

// Apply
add(2, 3)  // 5
square(4)  // 16

// Method to function (eta-expansion)
def multiply(a: Int, b: Int): Int = a * b
val mul = multiply _  // or just 'multiply' in Scala 3
mul(3, 4)  // 12

// Higher-order: function taking function
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(square, 2)  // 16

// Higher-order: function returning function
def adder(n: Int): Int => Int = _ + n
val add5 = adder(5)
add5(10)  // 15

// Function composition
val f: Int => Int = _ + 1
val g: Int => Int = _ * 2
val h = f andThen g  // f then g: (x+1)*2
val h2 = f compose g  // g then f: (x*2)+1
h(3)  // 8
h2(3)  // 7

柯里化与部分应用

柯里化将函数拆分为多个参数列表,实现部分应用(固定一些参数,为剩余参数获得一个函数)。这有助于类型推断(较早的参数约束较晚的参数)并创建可配置的函数(withDb(config))。占位符 _ 部分应用:sum(1, _) 创建一个函数。多个参数列表是 Scala 的柯里化机制。当某些参数是'配置'而其他是'数据'时使用柯里化。

scala
// Curried function: multiple parameter lists
def add(a: Int)(b: Int): Int = a + b
val add5: Int => Int = add(5)  // partial application
add5(10)  // 15

// Multiple parameter lists for type inference
def map[A, B](list: List[A])(f: A => B): List[B] =
  list.map(f)
map(List(1, 2, 3))(x => x * 2)  // A, B inferred from first list

// Curried form (Function types)
val curriedAdd: Int => Int => Int = a => b => a + b
curriedAdd(5)(10)  // 15

// Uncurrying
def uncurriedAdd(a: Int, b: Int): Int = a + b

// Practical: configuration via currying
def withDb(config: DbConfig)(f: Connection => Result): Result = ???
val withMyDb = withDb(myConfig) _
withMyDb { conn => /* ... */ }

// Partial application with placeholder
val sum: (Int, Int) => Int = _ + _
val addOne: Int => Int = sum(1, _)

纯函数与引用透明性

纯函数对相同输入总是返回相同输出,且没有副作用。它们易于测试、推理、并行化和组合。引用透明性意味着你可以用函数调用的结果替换调用而不改变行为。Scala 不强制纯度,但像 Cats Effect(IO monad)这样的库让你隔离副作用。纯核心 + 边缘 IO 是常见的 FP 架构:业务逻辑是纯的,I/O 包装在 IO 中。

scala
// Pure function: same input → same output, no side effects
def pureAdd(a: Int, b: Int): Int = a + b  // pure

// Impure: depends on external state
var counter = 0
def impureAdd(a: Int): Int = { counter += 1; a + counter }  // impure

// Impure: side effect
def impurePrint(a: Int): Int = { println(a); a }  // side effect

// Referential transparency: can replace call with result
val x = pureAdd(2, 3)
val y = x + x  // same as pureAdd(2, 3) + pureAdd(2, 3)

// Benefits of purity:
// 1. Easy to test (no setup/teardown)
// 2. Easy to reason about (no hidden state)
// 3. Parallelizable (no shared state)
// 4. Memoizable (cache results)
// 5. Composable (predictable)

// IO monad for side effects (Cats Effect)
import cats.effect.IO
val program: IO[Unit] = IO.println("hello")
val mapped: IO[String] = IO.pure("world").map(_.toUpperCase)
// Side effects captured in IO, run at edge of program

不可变数据与持久化集合

不可变数据结构在修改时返回新副本,内部共享结构以提高效率(持久化数据结构)。Scala 的集合默认不可变。case class copy 创建修改后的副本。对于深度更新,Lens 库(Monocle)提供可组合的访问器。不可变性消除了整类 bug(竞态条件、意外修改)并使代码更易推理。由于结构共享(O(log n) 而非 O(n)),性能成本通常可以接受。

scala
// Immutable: operations return new collections
val list1 = List(1, 2, 3)
val list2 = list1 :+ 4  // List(1, 2, 3, 4)
// list1 is unchanged: List(1, 2, 3)

// Persistent data structures: share structure (efficient)
val map1 = Map("a" -> 1, "b" -> 2)
val map2 = map1 + ("c" -> 3)  // shares structure with map1
// O(log n) due to structural sharing, not O(n) copy

// Case classes: copy for modification
case class User(name: String, age: Int)
val alice = User("Alice", 30)
val older = alice.copy(age = 31)  // new instance, alice unchanged

// Lens (Monocle library) for nested updates
import monocle.macros.GenLens
val ageLens = GenLens[User](_.age)
val updated = ageLens.modify(_ + 1)(alice)  // User("Alice", 31)

// Benefits:
// - No bugs from shared mutation
// - Easy to reason about (values don't change)
// - Free concurrency (no locks needed)
// - Undo/redo trivial (keep old versions)

递归与尾调用

尾递归(递归调用是最后一个操作)由 Scala 编译器优化为循环——无栈增长。@tailrec 在编译时验证这一点。累加器模式(传递累积结果)将非尾递归转换为尾递归。foldLeft/foldRight 封装了常见的递归模式。对于深度非尾递归,使用蹦床(Cats)或用 fold 重写。优先使用 fold 而非显式递归以获得清晰度——它们是尾递归且地道的。

scala
// Regular recursion (can stack overflow)
def factorial(n: Int): Int =
  if (n <= 1) 1 else n * factorial(n - 1)
factorial(10000)  // StackOverflowError!

// Tail recursion: compiler optimizes to loop
import scala.annotation.tailrec
@tailrec
def factorialTail(n: Int, acc: Int = 1): Int =
  if (n <= 1) acc else factorialTail(n - 1, n * acc)
factorialTail(10000)  // works (no stack overflow)

// @tailrec annotation: compiler verifies it's tail-recursive
// Error if not actually tail-recursive

// Trampoline for non-tail recursion
// (Cats' Trampoline or Free monads)
// Converts stack recursion to heap

// Fold as alternative to recursion
def sum(list: List[Int]): Int = list.foldLeft(0)(_ + _)
// foldLeft is tail-recursive internally

// Pattern: accumulator pattern
@tailrec
def reverse[A](list: List[A], acc: List[A] = Nil): List[A] =
  list match
    case Nil => acc
    case head :: tail => reverse(tail, head :: acc)
18

Case Class 与 ADT

Case Class 基础

Case class 是 Scala 的主要数据建模工具。编译器生成 equals、hashCode、toString、copy、apply、unapply(用于模式匹配)和访问器方法。它们默认不可变(val 字段)。将 case class 用于 DTO、值对象、消息和领域实体(不可变时)。伴生对象的 apply 让你无需 'new' 即可构造。copy 实现非破坏性更新。Case class 是 ADT(代数数据类型)的基础。

scala
// Case class: immutable data holder with boilerplate generated
case class Person(name: String, age: Int)

val alice = Person("Alice", 30)
val bob = Person("Bob", 25)

// Auto-generated methods:
alice.toString  // "Person(Alice,30)"
alice == Person("Alice", 30)  // true (structural equality)
alice.hashCode  // based on fields

// Pattern matching
alice match
  case Person(name, age) => s"$name is $age"

// Copy with modifications
val older = alice.copy(age = 31)  // Person("Alice", 31)

// Companion object with apply (no 'new' needed)
val p = Person("Charlie", 40)  // Person.apply called

// Fields accessed directly
alice.name  // "Alice"
alice.age   // 30

// Case class with default values
case class Point(x: Double = 0, y: Double = 0)
Point()  // Point(0.0, 0.0)
Point(y = 5)  // Point(0.0, 5.0)

Sealed Trait 与 ADT

ADT(代数数据类型)将数据建模为一组封闭的 case(sealed trait + case class/object)。'sealed' 意味着所有子类型在同一文件中,从而实现穷尽的模式匹配——如果你遗漏一个 case,编译器会警告。添加新 case 会显示所有需要更新的位置(安全重构)。Case object 是单例(无参数)。Option、List、Either 都是 ADT。这是函数式领域建模方式——每个变体是一个 case,行为在模式匹配中。

scala
// ADT: sealed trait + case classes/objects
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Triangle(a: Double, b: Double, c: Double) extends Shape

// Exhaustive pattern matching (compiler checks all cases)
def area(shape: Shape): Double = shape match
  case Circle(r) => math.Pi * r * r
  case Rectangle(w, h) => w * h
  case Triangle(a, b, c) =>
    val s = (a + b + c) / 2
    math.sqrt(s * (s-a) * (s-b) * (s-c))

// Adding a new case: compiler warns about non-exhaustive matches
case class Square(side: Double) extends Shape
// area now needs a Square case!

// Case objects for singletons (no parameters)
sealed trait Status
case object Active extends Status
case object Inactive extends Status
case object Pending extends Status

// Option and List are ADTs:
// sealed trait Option[+A]
// case class Some[A](value: A) extends Option[A]
// case object None extends Option[Nothing]

Case Class 特性

Case class 可以有方法、自定义 apply(用于验证)、实现 trait 和泛型。伴生对象中的自定义 apply 可以在构造前验证(使非法状态不可表示)。Case class 可以实现 trait 以实现多态。私有构造器(case class Email private)强制使用伴生对象的 apply 进行验证。这结合了数据建模与封装——构造器不会静默失败。

scala
// Case class with methods
case class Vec2(x: Double, y: Double) {
  def +(other: Vec2): Vec2 = Vec2(x + other.x, y + other.y)
  def magnitude: Double = math.sqrt(x*x + y*y)
  def dot(other: Vec2): Double = x*other.x + y*other.y
}

Vec2(1, 2) + Vec2(3, 4)  // Vec2(4.0, 6.0)
Vec2(3, 4).magnitude  // 5.0

// Case class with custom apply (validation)
case class Email private (value: String)
object Email {
  def apply(value: String): Email = {
    require(value.contains("@"), "invalid email")
    new Email(value)  // bypass public apply
  }
}
// Email("notanemail")  // IllegalArgumentException
Email("[email protected]")  // works

// Case class implementing trait
trait Jsonable {
  def toJson: String
}
case class User(name: String, age: Int) extends Jsonable {
  def toJson: String = s"""{"name":"$name","age":$age}"""
}

// Case class with type parameters
case class Box[A](value: A) {
  def map[B](f: A => B): Box[B] = Box(f(value))
}

模式匹配深入

模式匹配是 Scala 强大的解构工具。它支持:字面量模式(0、"hello")、类型模式(s: String)、case class 模式(User(name, age))、守卫(if condition)、嵌套模式和绑定(@)。编译器对 sealed 类型检查穷尽性。模式从上到下尝试。使用 @ 在提取部分的同时绑定整个值。模式匹配用一种统一、可读的语法替代 if-else 链、类型检查和解构。

scala
// Various pattern types
val x: Any = (1, "hello")

x match
  case (a: Int, b: String) => s"int $a, string $b"
  case (a, b) => s"pair: $a, $b"
  case _ => "other"

// Guards
def classify(n: Int): String = n match
  case n if n < 0 => "negative"
  case 0 => "zero"
  case n if n % 2 == 0 => "even positive"
  case _ => "odd positive"

// Case class patterns (nested)
case class Point(x: Int, y: Int)
case class Shape(center: Point, size: Int)

def isOrigin(s: Shape): Boolean = s match
  case Shape(Point(0, 0), _) => true
  case _ => false

// Named patterns (bind the whole while extracting)
case class User(name: String, age: Int)
def describe(u: User): String = u match
  case u @ User(name, age) if age < 18 => s"$name is a minor"
  case User(name, _) => s"$name is an adult"

// Type patterns
def handle(x: Any): String = x match
  case s: String => s"string: $s"
  case n: Int => s"int: $n"
  case list: List[_] => s"list of size ${list.size}"
  case None => "none"
  case _ => "unknown"

提取器与自定义模式

提取器(unapply)允许对任何类型进行模式匹配,而不仅是 case class。这将匹配模式与数据表示解耦。正则表达式是提取器(组成为绑定)。你可以为外部类型(JSON、URL)编写提取器而无需修改它们。unapply 返回 Option[(T1, T2, ...)] 用于提取,或 Boolean 用于简单匹配。提取器使模式匹配可扩展到任何数据源。Case class 自动生成 unapply;自定义提取器为非 case class 类型添加匹配。

scala
// Custom extractor via unapply
object Email {
  def unapply(str: String): Option[(String, String)] = {
    val parts = str.split("@")
    if (parts.length == 2) Some((parts(0), parts(1)))
    else None
  }
}

// Use in pattern matching
"[email protected]" match
  case Email(user, domain) => s"user: $user, domain: $domain"
  case _ => "not an email"

// Boolean extractor (no extracted values)
object Even {
  def unapply(n: Int): Boolean = n % 2 == 0
}

5 match
  case Even() => "even"
  case _ => "odd"

// Extractor with variable arity
object Pair {
  def unapply[A, B](t: (A, B)): Option[(A, B)] = Some(t._1, t._2)
}

// Regex as extractor
val Date = "(\\d{4})-(\\d{2})-(\\d{2})".r
"2024-01-15" match
  case Date(year, month, day) => s"$year/$month/$day"
  case _ => "not a date"

// Practical: parse without case classes
object Json {
  def unapply(s: String): Option[Any] =
    scala.util.Try(ujson.read(s)).toOption
}
19

集合深入

不可变集合

Scala 集合默认不可变。List 是链表(随机访问 O(n))。Vector 是树结构,访问 O(log n)。Map 和 Set 基于哈希。操作返回新集合,共享结构以提高效率。

scala
val list = List(1, 2, 3)
val vector = Vector(1, 2, 3)  // Fast random access
val set = Set(1, 2, 3)
val map = Map("a" -> 1, "b" -> 2)
// All immutable: operations return new collections
val updated = map + ("c" -> 3)  // New map

集合操作

map 转换元素。filter 选择。reduce 合并。grouped 分块。flatten 合并嵌套集合。flatMap 映射并扁平化。都返回新集合。惰性集合使用 .view 或 .iterator 以提高效率。

scala
val nums = (1 to 10).toList
val doubled = nums.map(_ * 2)
val evens = nums.filter(_ % 2 == 0)
val sum = nums.reduce(_ + _)
val grouped = nums.grouped(3).toList  // List(List(1,2,3), List(4,5,6), ...)
val flat = List(List(1,2), List(3,4)).flatten  // List(1,2,3,4)

模式匹配集合

模式匹配适用于集合。::(cons)匹配头部和尾部。List(a, b, c) 精确匹配 3 个元素。Nil 匹配空列表。_ 是通配符。用于解析和解构。穷尽匹配防止 bug。

scala
val list = List(1, 2, 3)
list match {
    case head :: tail => println(s"Head: $head")
    case Nil => println("Empty")
}
List(1, 2, 3) match {
    case List(a, b, c) => println(s"$a, $b, $c")
    case _ => println("Other")
}

可变集合

可变集合(ArrayBuffer、mutable.Set、mutable.Map)就地修改。频繁更新时更快但非线程安全。在性能重要且不需要不可变性时使用。用 .toMap、.toSet 转换为不可变。

scala
import scala.collection.mutable
val buffer = mutable.ArrayBuffer(1, 2, 3)
buffer += 4  // Add element
buffer -= 2  // Remove element
buffer(0) = 10  // Update
val mset = mutable.Set(1, 2, 3)
val mmap = mutable.Map("a" -> 1)
mmap("b") = 2

惰性集合

view 创建惰性视图:操作延迟到强制求值时执行。适用于大型集合上的链式操作。LazyList(Scala 2.13+)是惰性序列。通过惰性求值可以实现无限序列。用 .toList、.toArray 强制求值。

scala
val lazyView = (1 to 1000000).view.map(_ * 2).filter(_ > 100)
// No computation yet
val first = lazyView.head  // Only computes first
val result = lazyView.take(10).toList  // Only 10 elements processed
// LazyList (formerly Stream)
val fibs: LazyList[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map(_ + _)
20

隐式

隐式参数

隐式参数在作用域中时自动传递。编译器搜索匹配的隐式值。用于配置、类型类和上下文。可被显式覆盖。多个隐式必须具有不同类型。

scala
def connect(url: String)(implicit timeout: Int): Unit = {
    println(s"Connecting to $url with timeout $timeout")
}
implicit val defaultTimeout: Int = 5000
connect("http://example.com")  // Uses 5000
connect("http://example.com")(3000)  // Explicit override

隐式转换

隐式转换自动转换类型。可能很危险(意外转换)。implicit class 添加扩展方法。Scala 3 使用 given/using 和 extension 以获得清晰度。优先使用扩展方法而非原始转换。

scala
import scala.language.implicitConversions
implicit def intToString(n: Int): String = n.toString
val s: String = 42  // Converts via intToString
// Extension methods (Scala 3 preferred)
implicit class RichInt(val n: Int) extends AnyVal {
    def squared: Int = n * n
}
5.squared  // 25

类型类

类型类是特设多态的模式。Trait 定义行为,实例为特定类型提供实现。隐式解析找到实例。比继承更灵活。在 Cats 和 Shapeless 中常见。

scala
trait Show[A] {
    def show(a: A): String
}
object Show {
    implicit val intShow: Show[Int] = (a: Int) => a.toString
    implicit val stringShow: Show[String] = (a: String) => a
    def apply[A](a: A)(implicit s: Show[A]): String = s.show(a)
}
Show(42)  // "42"
Show("hello")  // "hello"

上下文边界

上下文边界 [A: TypeClass] 是隐式参数的语法糖。类型类实例通过 implicitly 可用。更简洁的类型类约束语法。常与 Ordering、Numeric 和自定义类型类一起使用。

scala
def max[A: Ordering](a: A, b: A): A = {
    val ord = implicitly[Ordering[A]]
    if (ord.gt(a, b)) a else b
}
// Equivalent to:
def max2[A](a: A, b: A)(implicit ord: Ordering[A]): A =
    if (ord.gt(a, b)) a else b

Given/Using(Scala 3)

Scala 3 用 given/using 替代隐式以获得清晰度。given 定义实例,using 声明参数。extension 替代 implicit class。更明确、更易读。迁移工具可转换 Scala 2 隐式。

scala
// Scala 3 syntax
given defaultTimeout: Int = 5000
def connect(url: String)(using timeout: Int): Unit =
    println(s"Connecting with $timeout")
connect("http://example.com")  // Uses given
// Extension methods
extension (n: Int)
    def squared: Int = n * n
21

并发

Future

Future 表示异步计算。onComplete 处理完成。map/flatMap 链式操作。ExecutionContext 提供线程。Future 是不可变且一次性的。对多个 Future 使用 for 推导式。

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f: Future[Int] = Future {
    Thread.sleep(1000)
    42
}
f.onComplete {
    case Success(v) => println(v)
    case Failure(e) => println(e)
}
// Map/flatMap for chaining
val f2 = f.map(_ * 2).flatMap(x => Future(x + 1))

Future 的 for 推导式

for 推导式脱糖为 flatMap/map。顺序执行:每步等待前一步。并行执行时,在 for 之前启动 Future。比嵌套回调可读得多。适用于任何 monad(Future、Option、List)。

scala
def getUser(id: Int): Future[User] = ...
def getOrders(user: User): Future[List[Order]] = ...
val result: Future[List[Order]] = for {
    user <- getUser(1)
    orders <- getOrders(user)
} yield orders
// Equivalent to flatMap/map chaining

并行 Future

在 for 推导式之前启动 Future 会并行运行它们。Future.sequence 将 List[Future[T]] 转换为 Future[List[T]]。Future.traverse 一步完成映射和序列化。zip 组合两个 Future。所有都在最慢的完成时完成。

scala
val f1 = Future { compute1() }
val f2 = Future { compute2() }
val combined: Future[(Int, Int)] = for {
    r1 <- f1
    r2 <- f2
} yield (r1, r2)
// Or: Future.sequence(List(f1, f2))
// Or: Future.traverse(list)(compute)

Actor(Akka)

Akka actor 封装状态并通过消息通信。无共享可变状态。每个 actor 一次处理一条消息。!(tell)发送即发即忘。?(ask)返回 Future。监督处理故障。非常适合有状态的并发系统。

scala
import akka.actor.*
class Counter extends Actor {
    var count = 0
    def receive = {
        case "inc" => count += 1
        case "get" => sender() ! count
    }
}
val system = ActorSystem("mySystem")
val counter = system.actorOf(Props[Counter], "counter")
counter ! "inc"
counter ! "get"

Cats Effect IO

Cats Effect IO 是纯函数式 IO monad。引用透明:IO(println("x")) 是一个值。与 for 推导式组合。可取消且资源安全。unsafeRunSync 在程序边缘运行。是 Future 的替代方案,语义更好。

scala
import cats.effect.IO
val program: IO[Int] = for {
    _ <- IO(println("Start"))
    result <- IO.pure(42)
    _ <- IO(println(s"Got $result"))
} yield result
// Run at end of world
program.unsafeRunSync()
// Referentially transparent, cancellable
22

模式匹配深入

Case Class

Case class 自动生成 equals、hashCode、toString 和 apply/unapply。非常适合模式匹配。模式匹配解构它们。默认不可变。用于代数数据类型。copy() 创建修改后的副本。

scala
case class Point(x: Int, y: Int)
val p = Point(1, 2)
p match {
    case Point(0, 0) => "origin"
    case Point(0, _) => "on y-axis"
    case Point(x, 0) => s"on x-axis at $x"
    case Point(x, y) => s"at ($x, $y)"
}

Sealed Trait

sealed trait 将子类型限制在同一文件中。编译器在模式匹配中检查穷尽性。添加新子类型会在所有匹配处产生警告。非常适合封闭类型层次结构。与 case class 结合用于 ADT。

scala
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Square(side: Double) extends Shape
case class Rectangle(w: Double, h: Double) extends Shape
def area(s: Shape): Double = s match {
    case Circle(r) => math.Pi * r * r
    case Square(s) => s * s
    case Rectangle(w, h) => w * h
}  // Compiler warns if not exhaustive

守卫与提取器

守卫(if)为模式添加条件。自定义提取器(unapply)允许对任何类型进行模式匹配。unapply 返回 Option 表示匹配。提取器将匹配与类型解耦。对 DSL 和解析非常强大。

scala
def classify(n: Int): String = n match {
    case x if x < 0 => "negative"
    case 0 => "zero"
    case x if x % 2 == 0 => "even"
    case _ => "odd"
}
// Custom extractor
object Even {
    def unapply(n: Int): Option[Int] =
        if (n % 2 == 0) Some(n / 2) else None
}
4 match { case Even(half) => s"half is $half" }

偏函数

偏函数仅对某些输入有定义。isDefinedAt 检查。collect 仅在定义处应用。用于回调和路由。orElse 组合偏函数。lift 转换为返回 Option 的全函数。

scala
val pf: PartialFunction[Int, String] = {
    case 1 => "one"
    case 2 => "two"
}
pf.isDefinedAt(1)  // true
pf.isDefinedAt(3)  // false
// Collect = filter + map
List(1, 2, 3, 1).collect(pf)  // List("one", "two", "one")

模式匹配类型

模式匹配适用于类型,但类型擦除影响泛型。List(a, b) 匹配 2 元素列表。List(_*) 匹配任何列表。避免匹配 List[Int] 等泛型类型(被擦除)。使用类型标签获取运行时类型信息。

scala
def describe(x: Any): String = x match {
    case i: Int => s"Int: $i"
    case s: String => s"String: $s"
    case List(a, b) => s"Two-element list: $a, $b"
    case List(_*) => "List with elements"
    case Some(v) => s"Some: $v"
    case None => "None"
    case _ => "Unknown"
}
23

函数式编程

高阶函数

高阶函数接收或返回函数。柯里化将多参数函数拆分为单参数链。部分应用固定一些参数。实现函数组合和复用。_ 创建部分应用的函数。

scala
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(_ + 3, 5)  // 11
// Currying
def add(a: Int)(b: Int): Int = a + b
val add5 = add(5) _  // Partial application
add5(3)  // 8

Option 与 Either

Option 表示可选值:Some 或 None。避免 null。getOrElse 提供默认值。Either 表示成功(Right)或失败(Left)。对于预期错误比异常更好。两者都是 monad:map、flatMap、for 推导式。

scala
def find(id: Int): Option[String] =
    if (id > 0) Some("Alice") else None
find(1).getOrElse("Unknown")  // "Alice"
find(-1).getOrElse("Unknown")  // "Unknown"
// Either for error handling
def parse(s: String): Either[String, Int] =
    try Right(s.toInt)
    catch { case _: Exception => Left(s"Not a number: $s") }

函数组合

compose 从右到左链式函数(类似数学)。andThen 从左到右链式(更易读)。两者都创建新函数。用于构建管道。函数在 Scala 中是一等值。

scala
val addOne: Int => Int = _ + 1
val double: Int => Int = _ * 2
// Compose (right to left)
val f = addOne compose double  // double then addOne
f(3)  // 7
// AndThen (left to right)
val g = addOne andThen double  // addOne then double
g(3)  // 8

递归与尾递归

尾递归由编译器优化为循环。递归调用必须是最后一个操作。@tailrec 注解在编译时验证。累加器模式携带状态。防止深度递归的栈溢出。

scala
// Not tail-recursive: stack overflow for large n
def factorial(n: Int): Int =
    if (n <= 1) 1 else n * factorial(n - 1)
// Tail-recursive: optimized to loop
import scala.annotation.tailrec
@tailrec
def factorial(n: Int, acc: Int = 1): Int =
    if (n <= 1) acc else factorial(n - 1, n * acc)

Monad

Monad 具有 flatMap 和 unit(pure/pure)。它们用上下文链式操作(Option:缺失、List:非确定性、Future:异步)。for 推导式脱糖为 flatMap/map。Monad 定律确保正确组合。Cats 提供 Monad 类型类。

scala
// Monad laws: left identity, right identity, associativity
// Option is a monad:
Some(5).flatMap(x => Some(x + 1))  // Some(6)
None.flatMap(x => Some(x + 1))  // None
// List is a monad:
List(1, 2).flatMap(x => List(x, x * 10))  // List(1, 10, 2, 20)
// for-comprehension is monadic sugar:
for {
    x <- Some(5)
    y <- Some(x + 1)
} yield y  // Some(6)
24

常见陷阱

Null 与 Option

Scala 有 null 用于 Java 兼容性,但不鼓励使用。Option 显式表示缺失。模式匹配强制处理 None。在 Try 上使用 .toOption 处理异常。在 Scala 代码中避免 null;保留用于 Java 互操作。

scala
// BAD: null
def find(id: Int): String =
    if (id > 0) "Alice" else null
// GOOD: Option
def find(id: Int): Option[String] =
    if (id > 0) Some("Alice") else None
// Scala avoids null; use Option
// NullPointer exceptions are rare in idiomatic Scala

Var 与 Val

val 是不可变的(值),var 是可变的(变量)。优先使用 val 以获得更安全、更可预测的代码。可变状态使推理和并发复杂化。仅在局部性能或真正需要时使用 var。集合默认不可变。

scala
var x = 1  // Mutable
x = 2  // OK
val y = 1  // Immutable
// y = 2  // Error
// Prefer val for immutability
// Use var only when necessary
// Mutable state causes bugs in concurrent code

相等性

Scala 的 == 调用 equals(值相等),不同于 Java。eq 检查引用相等性。ne 是 eq 的否定。始终使用 == 进行值比较。Case class 有正确的 equals。对于自定义类,重写 equals 和 hashCode。

scala
val a = List(1, 2)
val b = List(1, 2)
a == b  // true (value equality)
a eq b  // false (reference equality)
// In Java: a == b compares references
// In Scala: == calls equals (value)
// Use eq for reference equality (rarely needed)

隐式歧义

多个同类型的隐式会导致歧义错误。保持隐式作用域干净。使用特定类型(newtype)来区分。Scala 3 的 given/using 更清晰。避免隐式转换;它们会导致意外行为。

scala
implicit val s1: String = "hello"
implicit val s2: String = "world"
def greet(implicit s: String) = println(s)
greet  // Error: ambiguous implicit values
// Fix: only one implicit in scope
// Or: pass explicitly
greet(s1)

传名与传值

传名参数(=> T)延迟求值,每次使用时求值。传值参数在调用前求值一次。传名参数支持自定义控制结构(unless、while)。可能导致多次求值;如需要用 lazy val 缓存。

scala
// By-value: evaluated once
def log(msg: String) = println(msg)
log(expensiveComputation())  // Evaluated before call
// By-name: evaluated each use
def log(msg: => String) = println(msg)
log(expensiveComputation())  // Evaluated only if called
// Useful for lazy evaluation and control flow
def unless(cond: Boolean)(body: => Unit): Unit =
    if (!cond) body
25

Akka Actor

Actor 系统

ActorSystem 是 actor 的容器。actorOf 创建带名称的 actor。!(tell)异步发送消息。?(ask)发送并返回 Future。Actor 通过路径标识。terminate 优雅关闭系统。

scala
import akka.actor.*
val system = ActorSystem("MySystem")
val actor = system.actorOf(Props[MyActor], "myActor")
actor ! "Hello"  // Fire-and-forget
val future = actor ? "Query"  // Ask pattern
system.terminate()

Actor 生命周期

preStart 在创建时运行,postStop 在终止时运行。preRestart/postRestart 处理监督。Actor 在重启前停止,然后重新启动。内部状态在重启时丢失。使用 preStart 初始化,postStop 清理。

scala
class MyActor extends Actor {
    override def preStart(): Unit = println("Starting")
    override def postStop(): Unit = println("Stopped")
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
        println("Restarting")
        super.preRestart(reason, message)
    }
    def receive = {
        case "ping" => sender() ! "pong"
    }
}

监督

监督定义如何处理故障。Resume:以相同状态继续。Restart:以新状态重建。Stop:永久终止。Escalate:让父级处理。OneForOneStrategy 仅影响失败的子级。AllForOneStrategy 影响所有子级。

scala
class Supervisor extends Actor {
    override val supervisorStrategy = OneForOneStrategy() {
        case _: ArithmeticException => Resume  // Continue
        case _: NullPointerException => Restart  // Restart actor
        case _: Exception => Stop  // Stop actor
    }
    def receive = { case _ => }
}

路由器

路由器将消息分发到多个 actor。Pool 创建并管理工作者。Group 使用现有 actor。策略:RoundRobin、Random、SmallestMailbox、ConsistentHashing、ScatterGatherFirst。路由器通过并行化工作提高吞吐量。

scala
// Pool router: creates workers
val router = system.actorOf(
    RoundRobinPool(5).props(Props[Worker]),
    "router"
)
router ! "work"  // Distributed to one of 5 workers
// Group router: uses existing actors
val group = system.actorOf(
    RoundRobinGroup(paths).props(),
    "group"
)

持久化

PersistentActor 将事件保存到日志(事件溯源)。命令先验证,然后作为事件持久化。重启时,通过 receiveRecover 重放事件。状态从事件重建。实现从崩溃中恢复。对 Akka Typed 使用 EventSourcedBehavior。

scala
class Counter extends PersistentActor {
    var count = 0
    override def persistenceId = "counter-1"
    override def receiveCommand = {
        case "inc" => persist(Incremented) { event =>
            count += 1
        }
    }
    override def receiveRecover = {
        case Incremented => count += 1
    }
}

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。