快速入门 Scala 编程语言教程
Scala 编程语言快速入门:拥抱函数式与面向对象的融合之美
Scala 是一门现代、多范式(multi-paradigm)的编程语言,巧妙地融合了面向对象编程(OOP)和函数式编程(FP)的特性。它运行在 Java 虚拟机(JVM)上,并且可以与现有的 Java 代码和库无缝互操作,这使得它在工业界,特别是在大数据处理(如 Apache Spark)、并发编程(如 Akka)、Web 开发(如 Play Framework)等领域得到了广泛应用。Scala 以其富有表现力、简洁且类型安全的语法而闻名。本教程旨在为您提供一个快速入门 Scala 的路径,覆盖其核心概念和基本用法。
目标读者: 本教程适合对编程有基本了解(例如了解变量、函数、循环、类等概念),并希望快速掌握 Scala 基础知识的开发者。有 Java 经验会非常有帮助,但不是必需的。
1. 为什么选择 Scala?
在深入细节之前,我们先简单了解一下学习 Scala 的主要优势:
- 函数式编程支持: Scala 是一流的函数式编程语言。它鼓励使用不可变数据结构、高阶函数、模式匹配等特性,有助于编写更简洁、可维护、易于并发处理的代码。
- 面向对象特性: Scala 也是一门纯粹的面向对象语言。每个值都是对象,每个操作都是方法调用。它提供了类、对象、特质(Traits)等强大的 OOP 构建块。
- 与 Java 的互操作性: Scala 编译成 Java 字节码,运行在 JVM 上。这意味着你可以直接使用庞大的 Java 生态系统中的库,并且可以在 Scala 项目中无缝集成 Java 代码,反之亦然。
- 静态类型与类型推断: Scala 是静态类型的,这意味着编译器在编译时会检查类型错误,提高了代码的健壮性。同时,它拥有强大的类型推断系统,让你在很多时候不必显式声明类型,保持了代码的简洁性。
- 富有表现力的语法: Scala 允许你用更少的代码表达复杂的逻辑,提高了开发效率。
- 强大的并发模型: 通过 Akka 等库,Scala 提供了基于 Actor 模型的高效、容错的并发和分布式系统解决方案。
- 大数据领域的王者: Apache Spark 是用 Scala 编写的,学习 Scala 是深入理解和高效使用 Spark 的关键。
2. 环境搭建
要开始 Scala 编程,你需要安装以下软件:
- Java Development Kit (JDK): Scala 运行在 JVM 上,因此需要安装 JDK(推荐版本 8 或更高版本)。你可以从 Oracle官网 或 AdoptOpenJDK 等处下载安装。请确保设置了
JAVA_HOME
环境变量。 - Scala 构建工具 (sbt): sbt (Simple Build Tool) 是 Scala 社区最常用的构建工具,用于管理依赖、编译、测试和运行 Scala 项目。访问 scala-sbt.org 下载并安装 sbt。安装后,打开终端或命令提示符,运行
sbt sbtVersion
检查是否安装成功。 - 集成开发环境 (IDE): 虽然你可以使用任何文本编辑器,但强烈推荐使用支持 Scala 的 IDE,以获得代码补全、语法高亮、调试等功能。最流行的选择是 IntelliJ IDEA 配合 Scala 插件。安装 IntelliJ IDEA Community 或 Ultimate 版本后,在插件市场搜索并安装 "Scala" 插件。
验证安装:
打开终端,输入 scala -version
(如果单独安装了 Scala 发行版) 或通过 sbt 启动 Scala REPL (Read-Eval-Print Loop) 来验证:
bash
sbt console
进入 scala>
提示符后,你可以输入简单的 Scala 代码并立即看到结果。例如:
```scala
scala> println("Hello, Scala!")
Hello, Scala!
scala> 1 + 2
res0: Int = 3
scala> :quit // 退出 REPL
```
3. Scala 基础语法
3.1 第一个 Scala 程序
通常,一个独立的 Scala 应用程序需要一个包含 main
方法的 object
。object
在 Scala 中代表单例对象。
创建一个名为 HelloWorld.scala
的文件:
scala
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, Scala world!")
}
}
解释:
* object HelloWorld
: 定义了一个名为 HelloWorld
的单例对象。
* def main(args: Array[String]): Unit
: 定义了主方法。
* def
: 关键字,用于定义方法。
* main
: 方法名,程序的入口点。
* args: Array[String]
: 参数列表,接收命令行参数,类型为字符串数组 (Array[String]
)。
* : Unit
: 返回类型。Unit
类似于 Java 中的 void
,表示方法不返回任何有意义的值。
* println("Hello, Scala world!")
: 调用 println
函数打印字符串到控制台。注意,在 Scala 中,语句末尾的分号通常是可选的。
编译和运行 (使用 sbt):
1. 创建一个项目目录,例如 my-scala-app
。
2. 在 my-scala-app
目录下创建一个名为 src/main/scala
的子目录结构。
3. 将 HelloWorld.scala
文件放入 src/main/scala
目录下。
4. 在 my-scala-app
目录下创建一个名为 build.sbt
的文件(即使是空的也行,或者指定 Scala 版本:scalaVersion := "2.13.x"
或 3.x.x
)。
5. 在 my-scala-app
目录下打开终端,运行 sbt run
。sbt 会自动找到 main
方法并执行。
3.2 变量声明:val
与 var
Scala 有两种声明变量的方式:
val
(Value): 声明一个不可变的引用(类似于 Java 的final
变量)。一旦初始化,val
指向的对象引用就不能再改变。这是 Scala 推荐的方式,因为它有助于函数式编程和线程安全。var
(Variable): 声明一个可变的引用。你可以重新赋值给var
变量。
```scala
val immutableValue: String = "Hello" // 显式声明类型
// immutableValue = "World" // 编译错误!val 不能重新赋值
var mutableVariable: Int = 10 // 显式声明类型
mutableVariable = 20 // 正确,var 可以重新赋值
println(mutableVariable) // 输出 20
// 类型推断:Scala 编译器通常能自动推断类型
val inferredString = "Scala Rocks" // 类型推断为 String
val inferredInt = 100 // 类型推断为 Int
```
重点: 优先使用 val
,除非确实需要可变性。
3.3 基本数据类型
Scala 的基本数据类型与 Java 类似,但都是对象。
Byte
: 8位有符号整数Short
: 16位有符号整数Int
: 32位有符号整数Long
: 64位有符号整数 (以L
或l
结尾,如100L
)Float
: 32位浮点数 (以f
或F
结尾,如3.14f
)Double
: 64位浮点数 (默认浮点数类型)Char
: 16位 Unicode 字符 (用单引号,如'A'
)String
: 字符串序列 (用双引号,如"Scala"
)Boolean
:true
或false
Unit
: 表示无值,类似于 Java 的void
。只有一个实例()
。Null
: 表示null
引用 (尽量避免使用,使用Option
代替)。Nothing
: 所有类型的子类型,表示没有值(例如,函数抛出异常的返回类型)。Any
: 所有类型的超类型,根类型。AnyRef
: 所有引用类型 (非值类型,如String
, 自定义类) 的超类型,类似 Java 的Object
。AnyVal
: 所有值类型 (Int
,Double
,Boolean
等) 的超类型。
3.4 运算符
Scala 的运算符实际上是方法调用。例如,1 + 2
等价于 1.+(2)
。这使得你可以为自己的类定义运算符。常用运算符与 Java 类似:
- 算术运算符:
+
,-
,*
,/
,%
- 关系运算符:
==
,!=
,>
,<
,>=
,<=
(注意:==
在 Scala 中比较值(对于引用类型调用equals
),类似于 Java 的equals()
,而不是比较引用地址) - 逻辑运算符:
&&
(逻辑与),||
(逻辑或),!
(逻辑非) - 位运算符:
&
,|
,^
,~
,<<
,>>
,>>>
- 赋值运算符:
=
,+=
,-=
,*=
,/=
,%=
等 (仅适用于var
)
3.5 字符串插值
Scala 提供了方便的字符串插值功能:
s
插值器: 在字符串前加s
,可以直接嵌入变量。
scala
val name = "Alice"
val age = 30
println(s"My name is $name and I am $age years old.")
println(s"Next year, I will be ${age + 1} years old.") // 可以嵌入表达式f
插值器: 类似于printf
,可以格式化输出。
scala
val height = 1.75
println(f"My name is $name%s, age $age%d, height $height%.2f meters.") // %s 字符串, %d 整数, %.2f 两位小数浮点数raw
插值器: 类似于s
插值器,但不会对字符串中的转义字符(如\n
,\t
)进行处理。
scala
println(raw"This is a raw string.\nNo newline here.")
4. 控制结构
Scala 的控制结构与许多语言相似,但有一个重要区别:大多数控制结构是表达式 (Expressions),它们会返回一个值。
4.1 if/else
表达式
if/else
在 Scala 中是一个表达式,它会返回一个值。
```scala
val x = 10
val result = if (x > 5) "Greater than 5" else "Less than or equal to 5"
println(result) // 输出 "Greater than 5"
// if 表达式的返回值类型是两个分支类型的公共超类型
val y = 0
val resultTypeInferred = if (y > 0) 1 else "zero" // resultTypeInferred 类型是 Any (Int 和 String 的公共超类型)
println(resultTypeInferred) // 输出 "zero"
// 只有 if 没有 else 时,如果条件不满足,返回 Unit 类型的值 ()
val z = -1
val onlyIfResult = if (z > 0) println("Positive")
println(onlyIfResult) // 输出 ()
```
4.2 while
和 do-while
循环
Scala 支持 while
和 do-while
循环,但它们在函数式编程中不常用,因为它们依赖可变状态并且不返回值 (返回 Unit
)。通常推荐使用函数式的方法(如递归、map
、foreach
等)来代替。
```scala
var i = 0
while (i < 3) {
println(s"While loop iteration: $i")
i += 1 // 需要可变变量
}
var j = 0
do {
println(s"Do-while loop iteration: $j")
j += 1
} while (j < 3)
```
4.3 for
推导式 (For Comprehensions)
Scala 的 for
循环非常强大,称为推导式 (Comprehension)。它不仅仅用于迭代,还可以用于过滤、转换和生成新的集合。
基本迭代:
```scala
val numbers = List(1, 2, 3, 4, 5)
// 遍历集合
println("Simple iteration:")
for (num <- numbers) {
println(num)
}
// 迭代 Range
println("\nIterating over a Range:")
for (i <- 1 to 5) { // 1 to 5 包含 5
print(s"$i ")
}
println()
for (i <- 1 until 5) { // 1 until 5 不包含 5
print(s"$i ")
}
println()
```
带守卫 (Guard) 的过滤:
scala
println("\nIteration with filter (guard):")
for (num <- numbers if num % 2 == 0) { // 只处理偶数
println(s"Even number: $num")
}
多重生成器 (Nested Loops):
scala
println("\nNested loops:")
val letters = List('a', 'b')
for (num <- numbers; letter <- letters) { // 等价于嵌套循环
println(s"$num$letter")
}
使用 yield
生成新集合: for
推导式最有用的特性是使用 yield
关键字,它可以将每次迭代的结果收集到一个新的集合中。
```scala
println("\nUsing yield to create a new collection:")
val doubledNumbers = for (num <- numbers) yield num * 2
println(doubledNumbers) // 输出 List(2, 4, 6, 8, 10)
val filteredAndTransformed = for {
num <- numbers // 生成器
if num > 2 // 守卫 (过滤)
} yield s"Number $num" // 转换结果
println(filteredAndTransformed) // 输出 List("Number 3", "Number 4", "Number 5")
// 组合多个生成器和守卫
val combinations = for {
num <- List(1, 2)
letter <- List('x', 'y')
if num == 1 // 只用数字 1
} yield s"$num-$letter"
println(combinations) // 输出 List("1-x", "1-y")
```
5. 函数与方法
在 Scala 中,方法 (Method) 是类或对象的一部分,而函数 (Function) 是完整的对象,可以赋值给变量。语法上,定义方法使用 def
关键字。
5.1 定义方法
```scala
// 定义一个方法,接收两个 Int 参数,返回 Int
def add(x: Int, y: Int): Int = {
x + y // 最后一行的表达式的值就是方法的返回值,通常不需要 return 关键字
}
// 调用方法
val sum = add(5, 3)
println(s"Sum: $sum") // 输出 Sum: 8
// 如果方法体只有一行,可以省略花括号
def subtract(x: Int, y: Int): Int = x - y
// 返回类型可以被推断 (但推荐在公共 API 中显式声明)
def multiply(x: Int, y: Int) = x * y // 编译器推断返回类型为 Int
// 没有参数的方法可以省略括号 (调用时也省略)
def greeting: String = "Hello!"
println(greeting) // 调用时不加括号
// 返回 Unit 的方法 (过程 Procedure)
def printMessage(message: String): Unit = {
println(message)
}
// 可以省略 : Unit =
def printAnotherMessage(message: String) { // 注意,= 号被省略了,这是定义过程的旧语法,不推荐
println(message)
}
printMessage("This is a message.")
```
5.2 函数是头等公民
函数在 Scala 中是对象,可以像其他值一样传递。这意味着你可以:
* 将函数赋值给变量。
* 将函数作为参数传递给其他函数(高阶函数)。
* 让函数返回另一个函数。
```scala
// 将一个匿名函数 (lambda) 赋值给 val
val increment: Int => Int = (x: Int) => x + 1 // 类型 Int => Int 表示接收 Int 返回 Int 的函数
println(increment(5)) // 输出 6
// 类型推断可以简化
val double = (x: Int) => x * 2
println(double(4)) // 输出 8
// 参数类型也可以推断 (如果上下文允许)
def operateOnNumber(num: Int, operation: Int => Int): Int = {
operation(num)
}
val result1 = operateOnNumber(10, increment) // 传递函数 increment
println(result1) // 输出 11
val result2 = operateOnNumber(10, double) // 传递函数 double
println(result2) // 输出 20
val result3 = operateOnNumber(10, x => x / 2) // 直接传递匿名函数
println(result3) // 输出 5
// 定义返回函数的方法
def multiplier(factor: Int): Int => Int = {
(x: Int) => x * factor
}
val multiplyByThree = multiplier(3) // multiplyByThree 是一个函数 Int => Int
println(multiplyByThree(5)) // 输出 15
```
5.3 高阶函数 (Higher-Order Functions)
接收函数作为参数或返回函数的函数称为高阶函数。Scala 集合库大量使用了高阶函数,如 map
, filter
, flatMap
, foreach
, reduce
等。
```scala
val nums = List(1, 2, 3, 4, 5)
// map: 对集合中每个元素应用函数,返回新集合
val squared = nums.map(x => x * x) // 使用匿名函数
println(squared) // 输出 List(1, 4, 9, 16, 25)
// filter: 保留满足条件的元素,返回新集合
val evens = nums.filter(x => x % 2 == 0)
println(evens) // 输出 List(2, 4)
// foreach: 对每个元素执行操作 (返回 Unit)
nums.foreach(x => println(s"Processing $x"))
// 可以用占位符语法简化 (如果参数只使用一次)
nums.foreach(println(_)) // _ 代表每个元素
// reduce: 将集合元素组合成单个值
val sumOfNums = nums.reduce((acc, curr) => acc + curr) // acc 是累积值, curr 是当前元素
// 简化版
val sumOfNumsSimple = nums.reduce( + )
println(sumOfNumsSimple) // 输出 15
```
6. 面向对象编程 (OOP)
Scala 是纯粹的面向对象语言。
6.1 类 (Class)
定义类与 Java 类似,但更简洁。构造函数参数直接写在类名后面。
```scala
class Person(val firstName: String, val lastName: String, private var age: Int) {
// 字段 (firstName 和 lastName 自动成为 public val 字段)
// age 是 private var 字段
// 辅助构造函数 (必须先调用主构造函数或其他辅助构造函数)
def this(firstName: String, lastName: String) = {
this(firstName, lastName, 0) // 调用主构造函数
}
// 方法
def fullName(): String = s"$firstName $lastName"
def birthday(): Unit = {
if (age >= 0) age += 1
}
def currentAge: Int = age // Getter 方法
// 类体中的代码是主构造函数的一部分
println(s"Creating person: ${fullName()}")
}
// 创建类的实例
val person1 = new Person("John", "Doe", 30)
println(person1.firstName) // 访问 val 字段
// person1.firstName = "Jane" // 编译错误,val 不可变
println(person1.fullName()) // 调用方法
person1.birthday()
println(s"Age after birthday: ${person1.currentAge}")
val person2 = new Person("Jane", "Smith") // 使用辅助构造函数
println(s"${person2.fullName()}'s age: ${person2.currentAge}")
```
6.2 对象 (Object)
object
关键字定义一个单例对象 (Singleton Object),全局只有一个实例。常用于:
* 存放工具方法或常量 (类似 Java 的静态成员)。
* 实现应用程序的入口点 (main
方法)。
* 作为伴生对象 (Companion Object)。
```scala
object StringUtils { // 工具类对象
def isEmpty(s: String): Boolean = s == null || s.trim.isEmpty
val DefaultEncoding = "UTF-8"
}
println(StringUtils.isEmpty(" ")) // 输出 false
println(StringUtils.DefaultEncoding) // 输出 UTF-8
// main 方法通常放在 object 中
object MyApp {
def main(args: Array[String]): Unit = {
println("Starting my app...")
// ...
}
}
```
6.3 伴生对象 (Companion Object)
如果一个 class
和一个 object
在同一个源文件中且同名,它们互为伴生关系。
* 伴生类 (Companion Class) 和伴生对象 (Companion Object) 可以互相访问对方的私有成员。
* 伴生对象常用于定义工厂方法 (替代构造函数)、存放静态成员等。
```scala
// Person.scala 文件
class Person private (val name: String, val age: Int) { // 主构造函数设为 private
def greet(): Unit = println(s"Hi, I'm $name")
private def secret(): Unit = println("This is a secret.")
def accessObjectSecret(): Unit = {
println(s"Accessing object secret: ${Person.objectSecret}") // 访问伴生对象的私有成员
}
}
object Person { // 伴生对象
private val objectSecret = "Companion Object Secret"
// 工厂方法 (apply 方法很特殊,可以省略 new 调用)
def apply(name: String, age: Int): Person = {
println("Using apply factory method...")
new Person(name, age) // 可以访问 Person 类的私有构造函数
}
def apply(name: String): Person = {
new Person(name, 0) // 重载 apply 方法
}
def accessClassSecret(p: Person): Unit = {
p.secret() // 访问伴生类的私有成员
}
}
// 使用伴生对象的 apply 方法创建实例 (更简洁)
val p1 = Person("Alice", 25) // 等价于 Person.apply("Alice", 25),省略了 new
val p2 = Person("Bob") // 使用重载的 apply 方法
p1.greet()
p1.accessObjectSecret()
// p1.secret() // 错误,不能从外部访问私有方法
Person.accessClassSecret(p1) // 伴生对象可以访问私有成员
``
apply方法是一个特殊的方法,使得你可以像调用函数一样创建对象:
Person("Alice", 25)`。
6.4 Case 类 (Case Class)
Case 类是 Scala 中一种特殊的类,为建模不可变数据进行了优化。编译器会自动为 Case 类生成许多有用的方法:
- 不可变性: 构造参数默认是
public val
。 apply
方法: 在伴生对象中自动生成apply
方法,创建实例时无需new
关键字。unapply
方法: 用于模式匹配。toString
方法: 提供易读的字符串表示。equals
和hashCode
方法: 基于构造参数的值实现,适合用于比较和在集合中使用。copy
方法: 方便地创建对象的浅拷贝并修改部分字段。
```scala
case class Book(title: String, author: String, year: Int)
// 创建实例 (无需 new)
val book1 = Book("Scala for the Impatient", "Cay Horstmann", 2012)
val book2 = Book("Scala for the Impatient", "Cay Horstmann", 2012)
val book3 = Book("Programming in Scala", "Martin Odersky", 2008)
println(book1) // 自动生成 toString: Book(Scala for the Impatient,Cay Horstmann,2012)
println(book1.title) // 字段是 public val
// book1.title = "New Title" // 编译错误,不可变
println(book1 == book2) // true (基于值的比较)
println(book1 == book3) // false
// copy 方法
val updatedBook1 = book1.copy(year = 2021)
println(updatedBook1) // Book(Scala for the Impatient,Cay Horstmann,2021)
```
Case 类在函数式编程和模式匹配中极其常用。
6.5 特质 (Trait)
Trait (特质) 类似于 Java 8+ 的接口,但更强大。它们可以包含:
* 抽象方法 (没有实现)
* 具体方法 (有实现)
* 字段 (val 或 var)
一个类可以混入 (mix in) 多个 Trait (使用 with
关键字),实现了多重继承的效果 (继承实现,而非状态)。
```scala
trait Logger {
def log(message: String): Unit // 抽象方法
}
trait TimestampLogger extends Logger {
// 具体方法,重写了 log
override def log(message: String): Unit = {
println(s"${java.time.Instant.now()}: $message")
}
}
trait ShortLogger extends Logger {
val maxLength: Int // 抽象字段
override def log(message: String): Unit = {
println(message.substring(0, Math.min(message.length, maxLength)))
}
}
class ConsoleLogger extends Logger {
override def log(message: String): Unit = println(message) // 实现抽象方法
}
// 混入单个 Trait
class ServiceA extends TimestampLogger {
def process(): Unit = {
log("Processing in ServiceA")
}
}
val serviceA = new ServiceA
serviceA.process()
// 混入多个 Trait (从右向左线性化)
class ServiceB extends ConsoleLogger with TimestampLogger with ShortLogger {
override val maxLength: Int = 10 // 实现抽象字段
def run(): Unit = {
log("This is a very long message for ServiceB.") // 哪个 log 会被调用? -> ShortLogger (最右边的覆盖了前面的)
}
}
val serviceB = new ServiceB
serviceB.run() // 输出: This is a
```
Traits 是 Scala 实现模块化和代码复用的重要机制。
7. 函数式编程核心概念
7.1 不可变性 (Immutability)
优先使用 val
和不可变集合 (如 List
, Map
, Set
默认是不可变的)。不可变数据更容易推理,尤其是在并发环境中,因为它消除了状态变化带来的副作用和竞态条件。
```scala
val immutableList = List(1, 2, 3)
// immutableList = List(4, 5) // 错误,val 不能重新赋值
// immutableList(0) = 10 // 错误,List 是不可变的
val newList = immutableList :+ 4 // :+ 创建一个包含原列表元素和新元素的新列表
println(immutableList) // List(1, 2, 3) (原列表不变)
println(newList) // List(1, 2, 3, 4)
```
7.2 Scala 集合库
Scala 提供了丰富且强大的集合库,区分可变 (mutable) 和不可变 (immutable) 集合。默认导入的是不可变集合。
List
: 不可变链表,适合头 K 个元素操作 (prepend::
, head, tail)。Vector
: 不可变数组,提供近乎常数时间的随机访问和更新 (返回新 Vector)。通常性能优于List
(除了头部操作)。Set
: 不可变集合,元素唯一且无序。Map
: 不可变键值对集合。ArrayBuffer
: 可变数组 (需要导入scala.collection.mutable.ArrayBuffer
)。mutable.Map
,mutable.Set
: 可变版本的 Map 和 Set。
```scala
// 不可变 List
val list = List(1, 2, 3)
val listPrepended = 0 :: list // :: 是 List 的方法,用于在头部添加元素
println(listPrepended) // List(0, 1, 2, 3)
// 不可变 Vector
val vector = Vector(10, 20, 30)
val updatedVector = vector.updated(1, 25) // 返回新 Vector,索引 1 的元素变为 25
println(vector) // Vector(10, 20, 30)
println(updatedVector) // Vector(10, 25, 30)
// 不可变 Map
val map = Map("a" -> 1, "b" -> 2)
val valueB = map("b") // 访问值
val updatedMap = map + ("c" -> 3) // 添加新键值对,返回新 Map
println(map) // Map(a -> 1, b -> 2)
println(updatedMap) // Map(a -> 1, b -> 2, c -> 3)
// 使用可变集合
import scala.collection.mutable.ArrayBuffer
val buffer = ArrayBufferInt // 创建空的可变数组缓冲
buffer += 1 // 添加元素 (原地修改)
buffer += (2, 3)
buffer.prepend(0)
println(buffer) // ArrayBuffer(0, 1, 2, 3)
```
记住,函数式编程倾向于使用不可变集合,并通过转换操作 (如 map
, filter
) 生成新的集合,而不是原地修改。
7.3 Option 类型 (处理空值)
为了避免 NullPointerException
,Scala 推荐使用 Option
类型来表示一个值可能存在也可能不存在。Option
是一个有两个子类的泛型类型:
Some[T]
: 表示值存在,并包含该值。None
: 表示值不存在。
```scala
val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo")
val parisOption: Option[String] = capitals.get("France") // get 返回 Option[String]
val londonOption: Option[String] = capitals.get("UK")
println(parisOption) // Some(Paris)
println(londonOption) // None
// 安全地处理 Option
// 1. 使用 getOrElse 提供默认值
val paris = parisOption.getOrElse("Unknown")
val london = londonOption.getOrElse("Unknown")
println(s"Capital of France: $paris") // Capital of France: Paris
println(s"Capital of UK: $london") // Capital of UK: Unknown
// 2. 使用模式匹配 (推荐)
def printCapital(capitalOption: Option[String]): Unit = {
capitalOption match {
case Some(capital) => println(s"The capital is $capital")
case None => println("Capital not found.")
}
}
printCapital(parisOption)
printCapital(londonOption)
// 3. 使用 map/flatMap (函数式风格)
val upperParis = parisOption.map(capital => capital.toUpperCase) // 如果是 Some,应用函数;如果是 None,保持 None
val upperLondon = londonOption.map(_.toUpperCase) // 简化写法
println(upperParis) // Some(PARIS)
println(upperLondon) // None
// 示例:安全地获取长度
val maybeString: Option[String] = Some("Scala")
val lengthOption: Option[Int] = maybeString.map(.length) // Some(5)
val noneString: Option[String] = None
val noneLength: Option[Int] = noneString.map(.length) // None
``
Option
使用可以让代码更清晰地表达值的可选性,并通过编译器强制你处理
None` 的情况。
7.4 模式匹配 (Pattern Matching)
模式匹配是 Scala 最强大、最常用的特性之一,它是一种更强大的 switch
语句,可以匹配:
- 常量值
- 类型
- Case 类 (解构)
- 集合
Option
- 范围
- 正则表达式
- 使用守卫 (if 条件)
模式匹配使用 match
关键字,后面跟着一系列 case
分支。
```scala
def describe(x: Any): String = x match {
// 匹配常量值
case 1 => "One"
case "hello" => "A greeting"
case true => "Truth"
// 匹配类型
case s: String => s"A string with length ${s.length}"
case i: Int => s"An integer: $i"
case l: List[] => s"A list with ${l.size} elements" // _ 是通配符,匹配任意类型
case m: Map[, _] => s"A map with ${m.size} entries"
// 匹配 Case 类 (解构)
case Book(title, author, ) if author == "Cay Horstmann" => s"A book by Horstmann: '$title'"
case Book(title, , year) => s"A book '$title' from $year"
// 匹配 Option
case Some(value) => s"Got Some($value)"
case None => "Got None"
// 匹配元组 (Tuple)
case (a, b) => s"A tuple ($a, $b)"
// 默认情况 (通配符 _)
case _ => "Something else"
}
println(describe(1)) // One
println(describe("hello")) // A greeting
println(describe("world")) // A string with length 5
println(describe(List(1, 2, 3))) // A list with 3 elements
println(describe(book1)) // A book by Horstmann: 'Scala for the Impatient'
println(describe(book3)) // A book 'Programming in Scala' from 2008
println(describe(parisOption)) // Got Some(Paris)
println(describe(londonOption)) // Got None
println(describe((10, "Scala"))) // A tuple (10, Scala)
println(describe(3.14)) // Something else
``
match
模式匹配不仅用于表达式,还可以用于变量赋值、
for` 推导式和函数定义 (偏函数)。
8. sbt 基础
sbt 是管理 Scala 项目的标准工具。核心配置文件是项目根目录下的 build.sbt
。
一个简单的 build.sbt
文件:
```scala
// build.sbt
// 项目名称
name := "MyScalaProject"
// 项目版本
version := "0.1.0-SNAPSHOT"
// Scala 版本 (非常重要)
scalaVersion := "2.13.8" // 或者使用 Scala 3: "3.1.3"
// 添加库依赖
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.11" % Test // 添加 ScalaTest 测试库
libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % "2.6.19" // 添加 Akka Actor 库
// %% 会自动根据 scalaVersion 选择正确的 Scala 子版本 (e.g., _2.13)
// % Test 表示这个依赖只在测试时需要
```
常用 sbt 命令 (在项目根目录下运行):
sbt compile
: 编译src/main/scala
和src/test/scala
下的源代码。sbt run
: 运行项目中包含main
方法的主类 (如果多于一个会提示选择)。sbt test
: 运行src/test/scala
下的测试用例 (需要测试框架如 ScalaTest, MUnit)。sbt console
: 启动 Scala REPL,并将项目类路径和依赖加入其中,方便交互式测试。sbt package
: 打包项目成 JAR 文件 (通常在target/scala-x.y.z/
目录下)。sbt clean
: 清除target
目录下的编译产物。sbt update
: 下载或更新项目依赖。sbt
: 进入 sbt 交互式模式,可以在里面连续输入命令。
9. 总结与下一步
恭喜你!通过本教程,你已经了解了 Scala 编程语言的核心概念,包括:
- Scala 的基本特性和环境设置。
- 基础语法:
val
/var
,数据类型,运算符,字符串插值。 - 控制结构:
if/else
表达式,for
推导式。 - 函数和方法:定义,高阶函数,匿名函数。
- 面向对象:类,对象,伴生对象,Case 类,特质 (Trait)。
- 函数式编程:不可变性,强大的集合库,
Option
类型,模式匹配。 sbt
构建工具的基础用法。
Scala 是一门深度和广度都相当可观的语言,这篇入门教程只是冰山一角。要继续深入学习,你可以探索以下方向:
- 深入函数式编程: 学习
flatMap
、fold
/reduce
的更多用法,理解 Monad、Functor 等函数式概念 (虽然不直接使用这些术语也能写好 FP 代码),探索纯函数式编程库如 Cats 或 ZIO。 - 类型系统: 了解 Scala 强大的类型系统,包括泛型、类型参数化、协变与逆变、隐式转换/参数 (Scala 2) 或
given
/using
(Scala 3)。 - 并发编程: 深入学习 Akka 框架,掌握 Actor 模型,或者探索基于
Future
和Promise
的异步编程。 - 特定框架和库: 根据兴趣选择方向,如:
- Web 开发: Play Framework, Akka HTTP
- 大数据: Apache Spark
- 数据库访问: Slick, Doobie
- Scala 3: 如果你从现在开始学习,可以直接关注 Scala 3 的新特性,它在语法和概念上有一些重要的改进和简化 (例如新的
enum
、given
/using
替代隐式、缩进语法等)。 - 实践: 多写代码!尝试解决一些编程问题,参与开源项目,或者构建自己的小应用。
推荐资源:
- 官方文档: docs.scala-lang.org (包含教程、API 文档、语言规范等)
- Scala Exercises: scala-exercises.org (在线练习平台)
- 书籍:
- 《Scala for the Impatient》 (快节奏入门)
- 《Programming in Scala》 (俗称 "Stairway book",非常全面深入)
- 《Functional Programming in Scala》 (深入函数式编程理论)
- 在线课程: Coursera 上 Martin Odersky (Scala 创始人) 的系列课程。
Scala 的学习曲线可能比某些语言陡峭,但它所带来的表达能力、代码健壮性和解决复杂问题的能力是值得投入的。祝你在 Scala 的世界里探索愉快!