Scala 编程入门教程:从零开始掌握这门强大的语言
欢迎来到 Scala 的世界!如果你对编程充满好奇,或者是一位希望拓展技能的开发者,Scala 是一门值得深入学习的语言。它以其强大的表达能力、优雅的语法以及对函数式编程和面向对象编程的完美融合而闻名。本教程将带你从零开始,一步步了解 Scala 的核心概念,为你打开通往 Scala 编程世界的大门。
第一章:初识 Scala – 它是什么?为什么选择它?
1.1 什么是 Scala?
Scala 是一门现代化的多范式编程语言,设计初衷是为了解决 Java 在某些方面的不足,同时保留与 Java 生态系统的完全兼容性。Scala 的名字来源于 “Scalable Language”,意为可扩展的语言,这体现了其设计的核心理念:既能编写小型脚本,也能构建大型、复杂的系统。
Scala 运行在 Java 虚拟机(JVM)上,这意味着它可以无缝地利用庞大的 Java 类库,并且与 Java 代码进行互操作。它融合了面向对象编程(OOP)和函数式编程(FP)的特性,允许开发者根据具体需求选择最适合的编程风格,甚至在同一个项目中混合使用。
1.2 为什么选择 Scala?
学习 Scala 有许多引人注目的理由:
- 多范式支持: Scala 既是纯粹的面向对象语言(每个值都是对象),也提供强大的函数式编程能力(函数可以作为值传递,支持高阶函数、不可变数据结构等)。这使得开发者能够以更灵活、更富有表现力的方式解决问题。
- 强大的表达力: Scala 拥有简洁而强大的语法,可以用更少的代码完成复杂的功能。例如,模式匹配、类型推断、集合操作等特性大大提高了代码的可读性和开发效率。
- 静态类型: Scala 是一门静态类型语言,这意味着大部分类型错误可以在编译时被捕获,而不是在运行时导致意外。Scala 的类型系统非常强大和灵活,支持类型推断,很多时候你无需显式指定类型,编译器会帮你推断出来。
- 与 Java 的互操作性: 运行在 JVM 上意味着你可以直接在 Scala 代码中调用 Java 类库,反之亦然。这使得 Scala 项目可以轻松集成到现有的 Java 生态系统中,或者利用 Java 社区积累的丰富资源。
- 并发和分布式处理: Scala 在设计时就考虑到了并发和分布式计算的需求。Akka 等著名的并发框架就是用 Scala 编写的,使得构建响应式、容错性强的并发系统变得更加容易。
- 行业应用广泛: Scala 在大数据(Spark、Kafka)、金融服务、Web 开发(Play 框架)、后端服务等领域有着广泛的应用。许多知名公司都在使用 Scala。
1.3 Scala 的一些核心特性预览
- val 和 var:
val
定义不可变变量(类似 Java 的final
),var
定义可变变量。在 Scala 中,推荐优先使用val
。 - 类型推断: 很多时候编译器可以自动推断变量和函数的类型,减少冗余代码。
- 表达式导向: 在 Scala 中,几乎所有的构造(包括
if/else
、for
循环、try/catch
)都是表达式,它们会产生一个值。 - 函数是一等公民: 函数可以像普通变量一样被传递、存储和返回。支持高阶函数。
- Case Classes 和模式匹配:
case class
是专门为模式匹配设计的类,模式匹配是 Scala 中一个极其强大的控制结构,用于解构数据和进行条件判断。 - Trait: 类似 Java 8 的接口,但功能更强大,可以包含抽象方法和具体实现,支持多重继承(mixin)。
- 单例对象 (Singleton Objects): 使用
object
关键字定义,是类的单例实例,常用于工具类、主程序入口或伴生对象。
第二章:搭建 Scala 开发环境
在开始编写 Scala 代码之前,你需要搭建好开发环境。
2.1 安装 Java Development Kit (JDK)
Scala 运行在 JVM 上,所以首先确保你的系统安装了 JDK(推荐 JDK 8 或更高版本)。你可以从 Oracle 官网或 OpenJDK 社区下载并安装适合你操作系统的 JDK 版本。安装完成后,检查环境变量 JAVA_HOME
是否设置正确,并确保 java -version
命令能正常运行。
2.2 安装 Scala
有几种方式安装 Scala:
- 使用包管理器: 如果你使用 macOS (Homebrew)、Linux (apt-get, yum) 或 Windows (Chocolatey),可以使用对应的包管理器安装 Scala。例如,在 macOS 上:
brew install scala
。 - 手动下载: 从 Scala 官网(https://www.scala-lang.org/download/)下载与你安装的 JDK 版本兼容的 Scala 二进制包,然后将其解压到本地目录,并将
bin
目录添加到系统的 PATH 环境变量中。
安装完成后,打开终端或命令行,运行 scala -version
,你应该能看到安装的 Scala 版本信息。
2.3 使用 Scala REPL
REPL (Read-Eval-Print Loop) 是一个交互式环境,允许你直接输入 Scala 代码并立即看到结果。这对于学习和测试 Scala 语法非常有用。在终端输入 scala
命令即可进入 REPL。
“`bash
$ scala
Welcome to Scala 3.2.1 (OpenJDK 64-Bit Server VM, Java 11.0.17).
Type in expressions for evaluation. Or try :help.
scala> 1 + 1
val res0: Int = 2
scala> println(“Hello, REPL!”)
Hello, REPL!
“`
输入 :quit
或 :q
可以退出 REPL。
2.4 选择一个 IDE (集成开发环境)
虽然可以使用文本编辑器编写 Scala 代码,但一个好的 IDE 能极大地提高开发效率,提供代码高亮、自动补全、错误检查、调试等功能。
- IntelliJ IDEA: 这是最受欢迎的 Scala IDE,提供强大的 Scala 插件(Community Edition 即可,需要安装 Scala 插件)。强烈推荐初学者使用 IntelliJ IDEA。
- VS Code: 通过安装 Scala (Metals) 插件,VS Code 也能提供良好的 Scala 开发体验。
- Eclipse: 也可以通过 Scala IDE for Eclipse 插件支持 Scala。
安装并配置好你选择的 IDE,确保 Scala 插件已安装并能正常工作。
第三章:你的第一个 Scala 程序 – “Hello, World!”
现在环境已经搭建好了,让我们来编写第一个 Scala 程序。
在你的 IDE 中创建一个新的 Scala 项目,或者在一个文本编辑器中创建名为 HelloWorld.scala
的文件。
scala
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, World!")
}
}
保存文件。如果你使用 IDE,可以直接运行这个程序。如果使用命令行,可以通过 scalac
编译,然后用 scala
运行:
bash
$ scalac HelloWorld.scala
$ scala HelloWorld
Hello, World!
代码解释:
object HelloWorld
: 在 Scala 中,object
关键字定义一个单例对象。它既可以作为类的伴生对象(与同名的类一起),也可以作为一个独立的单例。在这里,HelloWorld
是一个独立的单例对象,我们用它来存放程序的主入口。def main(args: Array[String]): Unit
: 这是 Scala 应用程序的主方法,类似于 Java 的public static void main(String[] args)
.def
关键字用于定义方法。main
是方法名。(args: Array[String])
是方法的参数列表。args
是参数名,类型是Array[String]
(字符串数组),用于接收命令行参数。: Unit
指定方法的返回类型。Unit
类似于 Java 的void
,表示方法不返回任何有用的值。=
后面跟着方法体{ ... }
。
println("Hello, World!")
: 这是一个标准的库方法,用于将字符串输出到控制台。
这个简单的例子展示了 Scala 程序的基本结构,以及如何定义单例对象和主方法。
第四章:Scala 基础语法
让我们深入了解 Scala 的一些基本语法元素。
4.1 变量:val 和 var
Scala 有两种定义变量的方式:
-
val
: 定义不可变变量。一旦赋值,就不能再改变其值。推荐在 Scala 中优先使用val
,因为它有助于编写更安全、更易于理解和并行处理的代码。
“`scala
val greeting: String = “Hello” // 显式指定类型
// greeting = “Hi” // 这会导致编译错误!val number = 10 // 编译器会自动推断类型为 Int
// number = 20 // 编译错误!
* `var`: 定义可变变量。可以多次赋值。
scala
var message: String = “Initial message”
message = “New message” // 合法var counter = 0 // 类型推断为 Int
counter = 1
counter = counter + 1 // 合法
“`
重要提示: 在 Scala 中,提倡使用 val
来尽可能地保持数据不可变性,这与函数式编程的思想一致。只有当你确实需要改变变量的值时,才考虑使用 var
。
4.2 数据类型
Scala 的数据类型系统是面向对象的,所有的基本类型(如整数、布尔值)都是对象。这意味着你可以在这些类型上调用方法。
- 数值类型:
Byte
(8位带符号整数)Short
(16位带符号整数)Int
(32位带符号整数)Long
(64位带符号整数)Float
(32位浮点数)Double
(64位浮点数)
- 布尔类型:
Boolean
(true 或 false) - 字符类型:
Char
(16位 Unicode 字符) - 字符串类型:
String
(不可变字符序列,实际上是java.lang.String
) - Unit 类型: 表示没有有用的值,类似于 Java 的
void
。 - Null 和 Nothing 类型:
Null
是所有引用类型的子类型,表示空引用。Nothing
是所有类型的子类型,表示一个计算永不正常完成(例如抛出异常或无限循环)。它们在类型系统中扮演特定角色。
类型通常放在变量名或参数名后面,用冒号分隔,如 variableName: Type
。但由于类型推断,很多时候可以省略。
scala
val age: Int = 30
val pi: Double = 3.14159
val isScalaFun: Boolean = true
val firstLetter: Char = 'S'
val greeting: String = "Hello"
val result: Unit = println("Done") // println 返回 Unit
4.3 运算符
Scala 的运算符实际上是方法调用的一种特殊写法(中缀表示法)。例如,a + b
实际上是 a.+(b)
。这使得你可以像使用内置运算符一样使用任何方法。
- 算术运算符:
+
,-
,*
,/
,%
- 关系运算符:
==
,!=
,>
,<
,>=
,<=
- 逻辑运算符:
&&
,||
,!
- 位运算符:
&
,|
,^
,<<
,>>
,>>>
scala
val sum = 5 + 3
val isGreater = sum > 7
val andResult = true && false
4.4 控制结构:if/else,for 循环,while 循环
Scala 的控制结构与许多其他语言类似,但有一些重要的不同之处,特别是它们通常是“表达式”而不是“语句”。
-
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”// 如果没有 else 子句,并且 if 条件为假,则 if 表达式的结果是 Unit。
if (x > 100) println(“Very large”)
``
if
如果和
else分支产生不同类型的值,则整个
if/else` 表达式的类型是这两个类型的公共父类型。 -
for 循环:
Scala 的for
循环非常强大,常用于遍历集合。它可以使用生成器 (<-
)、守卫 (if
) 和yield
子句。- 简单遍历:
scala
val numbers = List(1, 2, 3, 4, 5)
for (number <- numbers) {
println(number)
} - 带索引遍历:
scala
val fruits = Array("apple", "banana", "cherry")
for (i <- fruits.indices) { // 遍历索引
println(s"Fruit at index $i is ${fruits(i)}")
} - 使用范围:
scala
for (i <- 1 to 5) println(i) // 包含 5
for (j <- 1 until 5) println(j) // 不包含 5 - 生成器和守卫:
scala
// 遍历 1 到 10 中的偶数
for (k <- 1 to 10 if k % 2 == 0) {
println(k)
} -
for-yield 表达式:
当for
循环包含yield
子句时,它不再是简单的循环,而是一个“for 推导式”,它会根据循环的每次迭代生成一个值,并将这些值收集到一个新的集合中。这是函数式风格中常用的模式。
“`scala
val doubledNumbers = for (number <- numbers) yield number * 2
println(doubledNumbers) // 输出 List(2, 4, 6, 8, 10)val evenNumbers = for (k <- 1 to 10 if k % 2 == 0) yield k
println(evenNumbers) // 输出 Vector(2, 4, 6, 8, 10)
``
for-yield推导式是 Scala 中处理集合的强大工具,通常比传统的
while` 循环更具表达力且不易出错。
- 简单遍历:
-
while 循环:
Scala 也支持传统的while
和do-while
循环,但它们是“语句”而不是“表达式”(它们的结果是Unit
)。在函数式风格的代码中,while
循环使用较少,通常由递归或集合的高阶函数替代。
“`scala
var i = 0
while (i < 5) {
println(i)
i += 1 // 等同于 i = i + 1
}var j = 0
do {
println(j)
j += 1
} while (j < 5)
“`
4.5 模式匹配 (Pattern Matching)
模式匹配是 Scala 中一个非常强大的特性,类似于其他语言中的 switch
语句,但更加灵活和强大。它可以用于匹配值、类型甚至集合的结构。
“`scala
val x = 3
x match {
case 1 => println(“One”)
case 2 => println(“Two”)
case 3 | 4 => println(“Three or Four”) // 多个值匹配
case _ => println(“Other”) // 默认匹配,类似于 default
}
// 模式匹配作为表达式
val result = x match {
case 1 => “Got one”
case y if y > 2 => s”Got a number greater than 2: $y” // 带守卫的模式
case _ => “Got something else”
}
println(result) // 输出 “Got a number greater than 2: 3”
``
case class` 结合使用时尤其强大。
模式匹配不仅限于基本类型,还可以用于匹配类的实例、集合的结构等,与
第五章:函数
函数是 Scala 中构建程序的基本模块。Scala 鼓励使用函数,并且函数是一等公民。
5.1 定义和调用函数
使用 def
关键字定义函数:
“`scala
def add(x: Int, y: Int): Int = {
x + y // 方法体,最后一行表达式的值作为返回值
}
// 单行方法可以省略花括号
def subtract(x: Int, y: Int): Int = x – y
// 返回 Unit 的方法,通常用于副作用(如打印)
def greet(name: String): Unit = {
println(s”Hello, $name!”)
}
// 调用函数
val sum = add(5, 3) // 结果为 8
val diff = subtract(10, 4) // 结果为 6
greet(“Scala”) // 输出 “Hello, Scala!”
“`
- 参数列表:
(parameterName: ParameterType, ...)
- 返回类型:
: ReturnType
。Scala 通常可以推断返回类型,尤其对于单行方法,但显式指定返回类型是好的实践,特别是在递归函数中。 - 方法体:
= { ... }
或= expression
。方法体的最后一个表达式的值就是方法的返回值。对于返回Unit
的方法,可以省略=
和方法体{}
,但保留花括号()
作为空参数列表:
scala
def sayHello() { // 返回 Unit
println("Hello!")
}
5.2 函数字面量 (匿名函数)
函数字面量是没有名字的函数,可以直接作为值传递。它们通常使用箭头 =>
定义:
scala
(parameter1: Type1, parameter2: Type2, ...) => function body
例如:
“`scala
val sumFunction = (x: Int, y: Int) => x + y
println(sumFunction(2, 3)) // 输出 5
val greetFunction = (name: String) => println(s”Greetings, $name!”)
greetFunction(“Scala user”) // 输出 “Greetings, Scala user!”
// 参数类型可以推断时省略
val multiply = (a, b) => a * b // 编译器会推断类型,取决于上下文
“`
函数字面量是实现高阶函数和简洁代码的关键。
5.3 高阶函数 (Higher-Order Functions)
高阶函数是接受其他函数作为参数,或返回一个函数的函数。这是函数式编程的核心特性之一。
“`scala
// 接受一个函数作为参数
def applyOperation(x: Int, y: Int, operation: (Int, Int) => Int): Int = {
operation(x, y)
}
val addOperation = (a: Int, b: Int) => a + b
val multiplyOperation = (a: Int, b: Int) => a * b
println(applyOperation(10, 5, addOperation)) // 输出 15
println(applyOperation(10, 5, multiplyOperation)) // 输出 50
// 直接传递匿名函数
println(applyOperation(20, 2, (a, b) => a – b)) // 输出 18
// 返回一个函数
def createMultiplier(factor: Int): Int => Int = {
(x: Int) => x * factor // 返回一个函数,这个函数接受 Int,返回 Int
}
val multiplyBy5 = createMultiplier(5)
println(multiplyBy5(10)) // 输出 50
“`
高阶函数是 Scala 集合库中大量使用的模式,如 map
, filter
, reduce
等。
第六章:面向对象编程 (OOP)
Scala 是一门纯粹的面向对象语言,每个值都是对象。它支持类、对象、继承和特质(trait)。
6.1 类和对象 (Classes and Objects)
-
类 (Class): 类的定义与许多其他语言类似,使用
class
关键字。
“`scala
class Person(name: String, age: Int) { // 主构造器参数
// 类体
val greeting = s”Hello, my name is $name and I am $age years old.” // 字段def sayHello(): Unit = { // 方法
println(greeting)
}
}// 创建类的实例
val person1 = new Person(“Alice”, 30)
person1.sayHello() // 输出 “Hello, my name is Alice and I am 30 years old.”
println(person1.greeting) // 访问字段
``
val
Scala 的类定义可以包含主构造器(在类名后面),以及辅助构造器。主构造器参数可以前面加上或
var` 使它们成为类字段。 -
对象 (Object): 使用
object
关键字定义单例对象。一个object
可以作为应用程序的入口点(包含main
方法),也可以用于存放工具方法或常量。
“`scala
object MathUtils { // 单例对象
def add(a: Int, b: Int): Int = a + b
def multiply(a: Int, b: Int): Int = a * b
}println(MathUtils.add(2, 3)) // 直接通过对象名调用方法
“` -
伴生对象和伴生类 (Companion Object and Class): 如果一个
class
和一个object
具有相同的名称,并且在同一个文件中定义,那么这个object
称为这个class
的伴生对象,这个class
称为这个object
的伴生类。它们可以互相访问对方的私有成员。伴生对象常用于定义工厂方法或与类相关的实用工具。
“`scala
class Circle(radius: Double) {
import Circle._ // 导入伴生对象中的成员
def area: Double = calculateArea(radius) // 调用伴生对象的方法
}object Circle { // 伴生对象
private def calculateArea(radius: Double): Double = Math.PI * radius * radius // 私有方法def apply(radius: Double): Circle = new Circle(radius) // 工厂方法,允许 Circle(radius) 这样创建实例
}val circle = Circle(5.0) // 调用伴生对象的 apply 方法,无需 new 关键字
println(circle.area)
“`
6.2 特质 (Traits)
特质是 Scala 中实现代码复用和多重继承(mixin)的重要机制。它们类似于 Java 8 中的默认方法接口。一个特质可以包含抽象方法和具体方法(有实现的方法)。一个类可以继承多个特质。
“`scala
// 定义特质
trait Greeter {
def greet(name: String): Unit // 抽象方法
}
trait Welcomer {
def welcome(name: String): Unit = { // 具体方法(带默认实现)
println(s”Welcome, $name!”)
}
}
// 类继承特质
class EnglishGreeter extends Greeter with Welcomer { // 使用 extends 关键字继承第一个特质,with 继承后续特质
override def greet(name: String): Unit = { // 实现抽象方法
println(s”Hello (from English), $name!”)
}
}
// 另一个类继承特质
class FrenchGreeter extends Greeter {
override def greet(name: String): Unit = {
println(s”Bonjour (from French), $name!”)
}
}
val english = new EnglishGreeter
english.greet(“Alice”) // 输出 “Hello (from English), Alice!”
english.welcome(“Bob”) // 输出 “Welcome, Bob!”
val french = new FrenchGreeter
french.greet(“Charlie”) // 输出 “Bonjour (from French), Charlie!”
// french.welcome(“David”) // 编译错误,FrenchGreeter 没有继承 Welcomer
“`
特质非常灵活,可以用于定义接口、共享行为、实现 AOP (面向切面编程) 等。
6.3 继承 (Inheritance)
Scala 使用 extends
关键字来实现类的继承。一个类只能直接继承一个父类,但可以继承多个特质。
“`scala
class Animal {
def speak(): Unit = {
println(“Animal makes a sound”)
}
}
class Dog extends Animal { // Dog 继承 Animal
override def speak(): Unit = { // override 关键字表示重写父类方法
println(“Woof!”)
}
def fetch(): Unit = {
println(“Fetching…”)
}
}
val animal: Animal = new Dog() // 多态性
animal.speak() // 输出 “Woof!”
val dog = new Dog()
dog.speak() // 输出 “Woof!”
dog.fetch() // 输出 “Fetching…”
``
override` 关键字是强制性的,当你重写父类方法时必须使用它,这有助于防止意外的方法签名错误。
6.4 Case Classes
case class
是一种特殊的类,主要用于模式匹配和作为不可变的数据结构。编译器会自动为 case class
生成一些有用的方法:
- 伴生对象: 包含一个
apply
工厂方法(允许创建实例时不使用new
关键字)和一个unapply
提取器方法(用于模式匹配)。 equals()
和hashCode()
方法: 基于构造器参数自动实现。toString()
方法: 提供友好的字符串表示。copy()
方法: 用于创建对象的副本,可以方便地修改其中一些字段的值。
“`scala
case class Point(x: Int, y: Int) // 简洁的定义
val p1 = Point(1, 2) // 使用 apply 方法,无需 new
val p2 = Point(1, 2)
println(p1) // 输出 Point(1,2) (toString)
println(p1 == p2) // 输出 true (equals)
val p3 = p1.copy(y = 3) // 创建副本并修改 y
println(p3) // 输出 Point(1,3)
// Case classes 在模式匹配中的应用
def describePoint(p: Point): String = p match {
case Point(0, 0) => “Origin”
case Point(x, 0) => s”On the x-axis at $x”
case Point(0, y) => s”On the y-axis at $y”
case Point(x, y) => s”Just a point at ($x, $y)”
}
println(describePoint(Point(0, 0))) // 输出 “Origin”
println(describePoint(Point(5, 0))) // 输出 “On the x-axis at 5”
``
case class` 是 Scala 函数式编程中构建不可变数据结构的首选方式,也是模式匹配的完美搭档。
第七章:函数式编程基础
虽然本教程只是入门,但理解 Scala 中的函数式编程思想非常重要。
7.1 不可变性 (Immutability)
函数式编程强调使用不可变数据。在 Scala 中,使用 val
来定义不可变变量,使用不可变集合(如 List
, Vector
, Set
, Map
)是实现不可变性的主要方式。对不可变集合的操作(如 map
, filter
, +
等)总是返回一个新的集合,而不是修改原集合。这使得代码更易于推理和并发安全。
“`scala
val originalList = List(1, 2, 3)
val newList = originalList.map(_ * 2) // map 返回新 List
println(originalList) // 输出 List(1, 2, 3) – 原列表未改变
println(newList) // 输出 List(2, 4, 6) – 新列表
“`
7.2 函数作为一等公民和高阶函数 (已在第五章介绍)
回顾第五章的内容,理解函数如何作为值传递和返回,以及高阶函数的使用,是 Scala 函数式编程的基础。
7.3 表达式导向 (Expression-Oriented) (已在第四章介绍)
回顾第四章,理解 if/else
、for-yield
、match
等结构作为表达式的重要性。这意味着它们会产生一个值,可以赋值给 val
。
7.4 常见高阶函数在集合上的应用
Scala 的集合库提供了丰富的函数式操作方法,它们都是高阶函数。
map
: 对集合中的每个元素应用一个函数,并返回一个新的集合。
scala
val names = List("Alice", "Bob", "Charlie")
val upperCaseNames = names.map(_.toUpperCase) // _.toUpperCase 是简写,等同于 (name => name.toUpperCase)
println(upperCaseNames) // 输出 List("ALICE", "BOB", "CHARLIE")filter
: 根据一个布尔函数(谓词)过滤集合中的元素,返回一个只包含满足条件的元素的新集合。
scala
val numbers = List(1, 2, 3, 4, 5, 6)
val evenNumbers = numbers.filter(_ % 2 == 0) // _ % 2 == 0 是简写,等同于 (num => num % 2 == 0)
println(evenNumbers) // 输出 List(2, 4, 6)-
reduce
/fold
: 将集合中的元素累积聚合成一个单一的值。reduce
从集合的第一个元素开始,fold
可以指定一个初始值。
“`scala
val sum = numbers.reduce( + ) // 计算所有元素的和
println(sum) // 输出 21val initialValue = 10
val foldedSum = numbers.fold(initialValue)( + ) // 10 + 1 + 2 + 3 + 4 + 5 + 6
println(foldedSum) // 输出 31
“`
这些函数式方法提供了声明式的方式来处理集合,使代码更加简洁、可读,并且易于并行化。
第八章:集合库
Scala 提供了一个丰富且强大的集合库,同时支持可变和不可变集合。默认情况下,Scala 倾向于使用不可变集合。
8.1 不可变集合 (Immutable Collections)
不可变集合在 scala.collection.immutable
包中定义。
- List: 不可变的链表。支持快速头操作 (
::
操作符),但随机访问较慢。
scala
val list1 = List(1, 2, 3)
val list2 = 0 :: list1 // 将 0 添加到 List 的开头,返回新 List
println(list2) // 输出 List(0, 1, 2, 3)
println(list1) // 输出 List(1, 2, 3) - 原 List 不变 - Vector: 不可变的向量。支持高效的随机访问和修改(尽管返回新 Vector)。通常作为大型不可变序列的首选。
scala
val vector1 = Vector(1, 2, 3)
val vector2 = vector1 :+ 4 // 在末尾添加元素,返回新 Vector
println(vector2) // 输出 Vector(1, 2, 3, 4)
println(vector1) // 输出 Vector(1, 2, 3) - 原 Vector 不变 - Set: 不可变的集合,元素唯一且无序。
scala
val set1 = Set(1, 2, 3, 2) // 重复的 2 会被忽略
println(set1) // 输出 Set(1, 2, 3) (顺序可能不同)
val set2 = set1 + 4 // 添加元素,返回新 Set
println(set2) // 输出 Set(1, 2, 3, 4) - Map: 不可变的映射,存储键值对。
scala
val map1 = Map("a" -> 1, "b" -> 2)
println(map1("a")) // 输出 1
val map2 = map1 + ("c" -> 3) // 添加键值对,返回新 Map
println(map2) // 输出 Map("a" -> 1, "b" -> 2, "c" -> 3)
创建集合时,通常不需要写immutable
,因为它们是默认的:List(...)
,Set(...)
,Map(...)
。
8.2 可变集合 (Mutable Collections)
可变集合在 scala.collection.mutable
包中定义。它们允许修改集合自身。
“`scala
import scala.collection.mutable.ArrayBuffer // 导入可变集合
val mutableList = scala.collection.mutable.ListBufferInt // 可变的 List
mutableList += 1
mutableList += 2
println(mutableList) // 输出 ListBuffer(1, 2)
val mutableMap = scala.collection.mutable.MapString, Int
mutableMap(“a”) = 1
mutableMap(“b”) = 2
println(mutableMap) // 输出 Map(“a” -> 1, “b” -> 2)
“`
可变集合在需要高性能修改操作或与 Java 集合交互时非常有用,但在并发环境中需要小心使用。
8.3 集合的常用操作
Scala 集合提供了大量的转换和操作方法,其中许多是高阶函数(如 map
, filter
, foreach
, fold
, reduce
等)。
“`scala
val numbers = List(1, 2, 3, 4, 5)
numbers.foreach(println) // 对每个元素执行副作用
val doubledAndFiltered = numbers
.map( * 2) // List(2, 4, 6, 8, 10)
.filter( > 5) // List(6, 8, 10)
println(doubledAndFiltered) // 输出 List(6, 8, 10)
val sortedList = List(3, 1, 4, 1, 5, 9).sorted
println(sortedList) // 输出 List(1, 1, 3, 4, 5, 9)
val uniqueElements = List(1, 2, 2, 3, 1).distinct
println(uniqueElements) // 输出 List(1, 2, 3)
“`
熟练掌握集合的函数式操作是编写 idiomatic Scala 代码的关键。
第九章:构建项目与 sbt
对于任何非trivial 的 Scala 项目,你需要一个构建工具来管理依赖、编译代码、运行测试等。sbt (Scala Build Tool) 是 Scala 生态中最常用的构建工具。
9.1 安装 sbt
从 sbt 官网(https://www.scala-sbt.org/download.html)下载适合你操作系统的安装包,并按照说明进行安装。安装完成后,在终端输入 sbt sbtVersion
应该能看到 sbt 的版本信息。
9.2 创建一个简单的 sbt 项目
创建一个新目录,例如 my-scala-project
。进入该目录,创建一个名为 build.sbt
的文件:
scala
lazy val root = project
.in(file(".")) // 项目根目录
.settings(
name := "my-scala-project", // 项目名称
version := "0.1.0-SNAPSHOT", // 项目版本
scalaVersion := "2.13.10" // 使用的 Scala 版本,根据实际安装版本调整
)
这个文件定义了项目的基本信息。lazy val root = project.in(file("."))
定义了一个名为 root
的项目,其根目录是当前目录。.settings(...)
中定义了项目的配置。
在项目根目录下创建目录结构:src/main/scala
。在这个目录下创建你的 Scala 源文件,例如 src/main/scala/Main.scala
:
scala
object Main {
def main(args: Array[String]): Unit = {
println("Hello from sbt project!")
}
}
9.3 运行 sbt 命令
在项目根目录下打开终端,运行 sbt
命令。sbt 会下载所需的组件,然后进入 sbt 命令行模式。
在 sbt 命令行中,你可以执行各种任务:
compile
: 编译项目代码。run
: 运行主程序(如果定义了main
方法)。clean
: 清理编译生成的文件。test
: 运行测试(如果定义了)。package
: 打包项目为 JAR 文件。reload
: 重新加载build.sbt
文件。:quit
或exit
: 退出 sbt 命令行。
你也可以直接在系统命令行中执行 sbt 任务,例如 sbt run
。
9.4 添加依赖
在 build.sbt
中,你可以使用 libraryDependencies
设置来添加外部库的依赖。语法通常是 organization %% artifactId % version
。%%
会根据当前的 scalaVersion
自动加上 Scala 版本后缀。
“`scala
lazy val root = project
.in(file(“.”))
.settings(
name := “my-scala-project”,
version := “0.1.0-SNAPSHOT”,
scalaVersion := “2.13.10”,
// 添加 Scalatest 测试库依赖
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % Test // % Test 表示只在测试范围内使用
)
``
sbt reload
添加依赖后,运行或直接运行任务(如
sbt compile`),sbt 会自动下载依赖库。
第十章:进阶之路与总结
恭喜你!你已经完成了 Scala 入门的基础学习。你现在应该对 Scala 的核心概念、语法以及如何搭建环境和使用构建工具有了基本的了解。
10.1 接下来学习什么?
这个入门教程仅仅触及了 Scala 的冰山一角。要进一步提升你的 Scala 技能,你可以深入学习:
- Scala 的类型系统: 深入理解类型推断、泛型、类型参数、类型约束、路径依赖类型等。
- 更高级的函数式编程: Currying、偏函数、函数组合、Option/Either/Try 等处理缺失值和错误的数据类型、副作用管理(IO)。
- 模式匹配的更多用法: 提取器 (Extractors)、Sealed Trait 和 Case Class 的联合使用。
- 隐式参数和隐式转换 (Implicits): 这是 Scala 中强大但有时难以理解的特性,用于上下文传递和扩展现有类的功能。
- 并发编程: Futures、Akka Actors 或 ZIO、Cats Effect 等现代并发库。
- 宏 (Macros): 在编译时进行代码生成和转换(高级主题)。
- Scala 3 的新特性: 枚举、新的隐式系统、Trait 参数等。
- 常用的 Scala 库和框架: Akka、Play、Scala.js、Scala Native、Spark 等。
10.2 学习资源
- Scala 官方文档: 始终是第一手资料。(https://docs.scala-lang.org/)
- Scala Book: 官方推荐的免费在线书籍。(https://docs.scala-lang.org/scala3/book/)
- Coursera 上的函数式编程课程: Martin Odersky(Scala 的创始人)教授的系列课程,非常经典。
- 《Programming in Scala》: Martin Odersky 等人撰写的经典 Scala 教材。
- 在线教程和博客: 搜索 Scala 相关的博客和教程网站。
- 参加社区: 加入 Scala 用户组、论坛、Discord 或 Slack 群组,与其他开发者交流。
10.3 总结
Scala 是一门充满活力和表现力的语言,它成功地将面向对象和函数式编程的优点结合在一起。掌握 Scala 需要时间和实践,特别是理解其函数式编程范式和强大的类型系统。
通过本教程的学习,你已经迈出了 Scala 学习的第一步。坚持练习,尝试编写小程序,阅读优秀的 Scala 代码,不断探索新的概念和库。随着你对 Scala 的深入了解,你会越来越体会到它在构建现代、健壮、可维护系统方面的优势。
祝你在 Scala 的学习旅程中取得成功!