cv.cm

编码 · 编程语言

Scala 入门教程

阅读约 9 分钟

简短回答

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
类型示例说明
Int4232 位有符号整数
Long42L64 位有符号整数
Double3.1464 位浮点数
Booleantruetrue 或 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 x

1 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 等函数式后端库的核心语言。

全部编码教程