编码 · 编程语言
Scala 入门教程
简短回答
Scala 是一门静态类型的 JVM 语言,融合了面向对象与函数式编程,广泛用于基于 Apache Spark 的数据工程和并发后端。它的主要特性有类型推断、不可变集合、样例类和模式匹配。本页以 Scala 3 为例,讲解环境搭建、值、流程控制、函数、集合、trait、枚举,以及用 Option、Either 和 Try 处理错误。
Scala 是什么,用来做什么
Scala 是一门融合面向对象与函数式编程的静态类型语言,由洛桑联邦理工学院(EPFL)的 Martin Odersky 创建,主要运行在 Java 虚拟机上,另有面向浏览器的 Scala.js 和生成原生程序的 Scala Native。Scala 代码可以使用任何 Java 库。
Scala 广泛用于数据工程(Apache Spark 就是用 Scala 编写的)、并发与分布式后端(Akka、Pekko、ZIO、Cats Effect)以及金融和流处理系统。本教程使用 Scala 3 语法,它允许用缩进划分代码块,大括号依然可用。
安装 Scala 并运行 Hello, world
推荐使用 Coursier 安装(cs setup),它会安装 scala 命令和 JDK。在 Scala 3 中,带有 @main 注解的方法就是程序入口。
// hello.scala
@main def hello(): Unit =
println("Hello, world!")scala run hello.scala
# Hello, world!较大的项目通常使用 sbt 和 Mill 作为构建工具。Scastie(scastie.scala-lang.org)可以在浏览器中运行 Scala,无需安装任何东西。
值、变量与基本类型
用 val 声明不可变的值,用 var 声明可变变量。类型会被推断,也可以写在冒号之后。地道的 Scala 代码几乎处处使用 val。
val name = "Scala" // String
var count: Int = 1 // mutable
count += 1
val ratio = 3.0 / 4 // Double: 0.75
val big = 12345678901L // Long
val ok = count > 1 // Boolean
println(s"$name: count=$count ratio=$ratio ok=$ok")
// Scala: count=2 ratio=0.75 ok=true
println(f"pi is ${math.Pi}%.3f") // pi is 3.142| 类型 | 示例 | 说明 |
|---|---|---|
Int | 42 | 32 位有符号整数 |
Long | 42L | 64 位有符号整数 |
Double | 3.14 | 64 位浮点数 |
Boolean | true | true 或 false |
String | "hi" | Java 字符串;支持 s"..." 和 f"..." 插值器 |
Unit | () | 类似 void:没有有意义的值 |
Scala 中一切皆表达式:if、代码块和 match 都会返回值,所以你很少需要临时的 var。
流程控制与模式匹配
if 会返回值。for 可遍历范围和集合,配合 yield 则会构建新的集合。match 是 Scala 强大的模式匹配,也是这门语言的标志性特性。
val n = 7
val parity = if n % 2 == 0 then "even" else "odd"
println(parity) // odd
for i <- 1 to 3 do print(s"$i ") // 1 2 3
println()
val squares = for i <- 1 to 4 yield i * i
println(squares) // Vector(1, 4, 9, 16)
def describe(x: Any): String = x match
case 0 => "zero"
case i: Int if i < 0 => "negative int"
case i: Int => s"int $i"
case s: String => s"string of length ${s.length}"
case (a, b) => s"pair $a and $b"
case _ => "something else"
println(describe(-5)) // negative int
println(describe("hey")) // string of length 3
println(describe((1, "x"))) // pair 1 and x1 to 3 包含 3;1 until 3 在 3 之前停止。最后的 case _ 是通配符,可匹配任何值;如果没有它,未匹配的值会抛出 MatchError。
函数与高阶函数
方法用 def 定义。函数本身就是值:可以存储、作为参数传递,也可以作为返回值。参数可以有默认值,调用时可以使用具名参数。
def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
s"$greeting, $name!"
val double = (x: Int) => x * 2 // function value
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(add(2, 3)) // 5
println(greet("Ann")) // Hello, Ann!
println(greet("Ann", greeting = "Hi")) // Hi, Ann!
println(applyTwice(double, 5)) // 20
println(List(1, 2, 3, 4).filter(_ % 2 == 0).map(_ * 10)) // List(20, 40)_ * 10 中的下划线是单参数 lambda x => x * 10 的简写。
集合
标准集合默认是不可变的:map、filter、:+ 等操作会返回新集合,原集合保持不变。可变版本位于 scala.collection.mutable 中。
val nums = List(5, 3, 8)
val more = nums :+ 1 // List(5, 3, 8, 1); nums unchanged
println(more.sorted) // List(1, 3, 5, 8)
println(nums.sum) // 16
val v = Vector("a", "b", "c") // fast indexed access
println(v(1)) // b
val ages = Map("Ann" -> 31, "Bo" -> 25)
println(ages("Ann")) // 31
println(ages.get("Zed")) // None
println(ages.getOrElse("Zed", 0)) // 0
val unique = Set(1, 2, 2, 3)
println(unique.size) // 3
val words = List("apple", "avocado", "banana")
println(words.groupBy(_.head)('a')) // List(apple, avocado)| 集合 | 适用场景 |
|---|---|
List | 不可变链表;在头部添加元素以及访问头/尾都很快 |
Vector | 不可变的索引序列;通用场景下的良好默认选择 |
Map | 键值查找 |
Set | 元素唯一,以及判断成员关系 |
Array | 可变的定长 Java 数组 |
样例类、trait 与枚举
case class(样例类)是不可变的数据类型,自动获得相等性比较、toString、copy 以及模式匹配支持。trait 类似接口,但也可以包含代码。Scala 3 新增了 enum,用于表示代数数据类型。
trait Shape:
def area: Double
case class Circle(r: Double) extends Shape:
def area: Double = math.Pi * r * r
case class Rect(w: Double, h: Double) extends Shape:
def area: Double = w * h
enum Color:
case Red, Green, Blue
@main def shapes(): Unit =
val all: List[Shape] = List(Circle(1), Rect(2, 3))
all.foreach(s => println(f"$s area=${s.area}%.2f"))
// Circle(1.0) area=3.14
// Rect(2.0,3.0) area=6.00
val r = Rect(2, 3)
println(r == Rect(2, 3)) // true
println(r.copy(h = 10)) // Rect(2.0,10.0)
println(Color.values.toList) // List(Red, Green, Blue)其他标志性特性还有 given 和 using(上下文参数,以前称为隐式参数)、扩展方法、不透明类型,以及 Int | String 这样的联合类型。
用 Option、Either 和 Try 处理错误
Scala 支持 try、catch 和 finally,但地道的代码会用类型来表达失败。Option 要么是 Some(value),要么是 None;Either 包含 Left 错误或 Right 结果;Try 则把异常捕获为 Failure。
import scala.util.{Try, Success, Failure}
def parseInt(s: String): Option[Int] = s.toIntOption
def safeDivide(a: Int, b: Int): Either[String, Int] =
if b == 0 then Left("division by zero") else Right(a / b)
println(parseInt("42")) // Some(42)
println(parseInt("x")) // None
println(safeDivide(10, 2)) // Right(5)
println(safeDivide(1, 0)) // Left(division by zero)
Try("abc".toInt) match
case Success(n) => println(s"got $n")
case Failure(e) => println(s"failed: ${e.getClass.getSimpleName}")
// failed: NumberFormatException一个完整的小程序:成绩报告
这个程序解析形如 name,score 的行,跳过无效行,并输出每位学生的平均分。它用到了样例类、Option、groupBy 和字符串格式化。
case class Score(name: String, value: Int)
def parse(line: String): Option[Score] =
line.split(",").map(_.trim) match
case Array(name, v) => v.toIntOption.map(Score(name, _))
case _ => None
@main def report(): Unit =
val lines = List("ann, 90", "bo, 70", "ann, 80", "broken line", "bo, x")
val scores = lines.flatMap(parse)
val averages = scores.groupBy(_.name).view.mapValues { list =>
list.map(_.value).sum.toDouble / list.size
}.toList.sortBy(_._1)
for (name, avg) <- averages do
println(f"$name%-4s $avg%.1f")
// ann 85.0
// bo 70.0接下来学什么
- docs.scala-lang.org 上的 Scala 3 Book,从基础到上下文抽象,完整讲解这门语言。
- Scala Toolkit:用于处理文件、JSON 和 HTTP 等日常任务。
- 如果想用 Scala 做数据处理,请阅读 Apache Spark 文档。
- scala-lang.org 上链接的课程,包括 EPFL 的函数式编程课程。
常见问题
应该学 Scala 2 还是 Scala 3?
新项目请学 Scala 3。许多现有代码库(包括 Spark 生态的部分内容)仍在使用 Scala 2.13,不过大多数核心概念是相通的。
Scala 是函数式语言吗?
Scala 既是面向对象的,也是函数式的。每个值都是对象,但函数是一等公民,集合默认不可变,模式匹配处于核心地位。
Scala 中的 val 和 var 有什么区别?
val 定义不能重新赋值的值,var 定义可以重新赋值的变量。地道的 Scala 代码更倾向于 val 搭配不可变集合。
什么是样例类?
样例类是用于表示不可变数据的类。编译器会自动生成结构相等性比较、可读的 toString、copy 方法以及对模式匹配的支持。
Scala 现在还有人用吗?
有。它仍是 Apache Spark、流处理与数据平台,以及 ZIO、Cats Effect 等函数式后端库的核心语言。