Scala 编程入门教程 – wiki基地


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/elsefor 循环、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”)
    ``
    如果
    ifelse分支产生不同类型的值,则整个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 也支持传统的 whiledo-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) // 访问字段
    ``
    Scala 的类定义可以包含主构造器(在类名后面),以及辅助构造器。主构造器参数可以前面加上
    valvar` 使它们成为类字段。

  • 对象 (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/elsefor-yieldmatch 等结构作为表达式的重要性。这意味着它们会产生一个值,可以赋值给 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) // 输出 21

    val 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 文件。
  • :quitexit: 退出 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 的学习旅程中取得成功!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部