Skip to content

Scala 变量

变量是程序中存储数据的基本单元。本章将详细介绍 Scala 中变量的声明、初始化和使用方法。

变量声明

Scala 提供两种变量声明方式:val(不可变)和 var(可变)。

val - 不可变变量

scala
// 基本声明
val name = "Alice"          // 类型推断为 String
val age = 25               // 类型推断为 Int
val height = 5.6           // 类型推断为 Double

// 显式类型声明
val count: Int = 10
val message: String = "Hello, Scala!"
val isActive: Boolean = true

// 一旦赋值,不能再次修改
// name = "Bob"  // 编译错误!

var - 可变变量

scala
// 基本声明
var counter = 0            // 类型推断为 Int
var status = "pending"     // 类型推断为 String

// 显式类型声明
var score: Double = 0.0
var items: List[String] = List.empty

// 可以重新赋值
counter = counter + 1      // OK
status = "completed"       // OK
score = 95.5              // OK

选择 val 还是 var

scala
// 推荐:优先使用 val
val configuration = Map(
  "host" -> "localhost",
  "port" -> 8080,
  "timeout" -> 30
)

// 只在必要时使用 var
var attempts = 0
while (attempts < 3) {
  // 尝试连接
  attempts += 1
}

// 函数式风格替代 var
val results = (1 to 3).map { attempt =>
  // 尝试连接并返回结果
  s"Attempt $attempt"
}

变量初始化

立即初始化

scala
val immediate = 42         // 立即初始化
var mutable = "initial"    // 立即初始化

延迟初始化

scala
// lazy val - 延迟初始化,只在首次访问时计算
lazy val expensiveComputation = {
  println("Computing...")
  Thread.sleep(1000)  // 模拟耗时操作
  42 * 42
}

println("Before access")
println(expensiveComputation)  // 此时才执行计算
println(expensiveComputation)  // 直接返回缓存的结果

条件初始化

scala
val environment = sys.env.getOrElse("ENV", "development")

val config = if (environment == "production") {
  Map("db" -> "prod-db", "cache" -> "redis")
} else {
  Map("db" -> "test-db", "cache" -> "memory")
}

// 使用 Option 处理可能不存在的值
val maybePort: Option[Int] = sys.env.get("PORT").map(_.toInt)
val port = maybePort.getOrElse(8080)

作用域和生命周期

局部变量

scala
def example(): Unit = {
  val localVar = "local"     // 方法作用域
  
  if (true) {
    val blockVar = "block"   // 块作用域
    println(localVar)        // 可以访问外层变量
    println(blockVar)        // 可以访问当前块变量
  }
  
  // println(blockVar)        // 编译错误:超出作用域
}

变量遮蔽

scala
val x = 10                   // 外层变量

def shadowExample(): Unit = {
  val x = 20                 // 遮蔽外层变量
  println(x)                 // 输出 20
  
  {
    val x = 30               // 进一步遮蔽
    println(x)               // 输出 30
  }
  
  println(x)                 // 输出 20
}

类成员变量

scala
class Person(val name: String, var age: Int) {
  // 主构造函数参数自动成为成员变量
  
  private val id = java.util.UUID.randomUUID()  // 私有成员
  protected var status = "active"                // 受保护成员
  
  def birthday(): Unit = {
    age += 1  // 修改可变成员变量
  }
  
  def getId: String = id.toString  // 访问私有成员
}

val person = new Person("Alice", 25)
println(person.name)     // 访问 val 成员
person.age = 26         // 修改 var 成员
person.birthday()       // 通过方法修改

类型推断

基本类型推断

scala
val int = 42              // Int
val long = 42L            // Long
val double = 3.14         // Double
val float = 3.14f         // Float
val string = "hello"      // String
val boolean = true        // Boolean
val char = 'A'           // Char

// 集合类型推断
val list = List(1, 2, 3)           // List[Int]
val map = Map("a" -> 1, "b" -> 2)  // Map[String, Int]
val set = Set(1, 2, 3, 2)          // Set[Int]

复杂类型推断

scala
// 函数类型推断
val add = (x: Int, y: Int) => x + y  // (Int, Int) => Int
val square = (x: Int) => x * x       // Int => Int

// 高阶函数类型推断
val numbers = List(1, 2, 3, 4, 5)
val doubled = numbers.map(_ * 2)     // List[Int]
val filtered = numbers.filter(_ > 2) // List[Int]

// Option 类型推断
val some = Some(42)                  // Some[Int]
val none = None                      // None.type
val option: Option[Int] = if (true) Some(42) else None

需要显式类型的情况

scala
// 空集合需要类型注解
val emptyList: List[String] = List()
val emptyMap: Map[String, Int] = Map()

// 递归函数需要返回类型
def factorial(n: Int): Int = {
  if (n <= 1) 1 else n * factorial(n - 1)
}

// 重载方法调用时可能需要类型注解
def process(x: Int): String = s"Int: $x"
def process(x: String): String = s"String: $x"

val result: String = process(42)  // 明确调用 Int 版本

变量模式匹配

解构赋值

scala
// 元组解构
val tuple = ("Alice", 25, "Engineer")
val (name, age, profession) = tuple

// 列表解构
val list = List(1, 2, 3, 4, 5)
val head :: tail = list              // head = 1, tail = List(2, 3, 4, 5)
val first :: second :: rest = list   // first = 1, second = 2, rest = List(3, 4, 5)

// 数组解构
val Array(a, b, c) = Array(1, 2, 3)

// 案例类解构
case class Point(x: Int, y: Int)
val point = Point(3, 4)
val Point(x, y) = point             // x = 3, y = 4

部分解构

scala
val numbers = List(1, 2, 3, 4, 5)

// 只取前两个元素
val first :: second :: _ = numbers

// 使用通配符忽略不需要的部分
val (name, _, profession) = ("Alice", 25, "Engineer")

// Option 解构
val maybeValue: Option[String] = Some("hello")
val Some(value) = maybeValue  // 危险:如果是 None 会抛异常

// 安全的 Option 处理
maybeValue match {
  case Some(v) => println(s"Value: $v")
  case None => println("No value")
}

变量的不变性

深度不变性 vs 浅度不变性

scala
// val 只保证引用不变,不保证内容不变
val mutableList = scala.collection.mutable.ListBuffer(1, 2, 3)
// mutableList = scala.collection.mutable.ListBuffer(4, 5, 6)  // 错误
mutableList += 4  // OK:修改内容

// 真正的不变性
val immutableList = List(1, 2, 3)
// immutableList = List(4, 5, 6)  // 错误
val newList = immutableList :+ 4  // OK:创建新列表

// 不可变的案例类
case class Person(name: String, age: Int)
val person = Person("Alice", 25)
// person.age = 26  // 错误:案例类字段默认是 val
val olderPerson = person.copy(age = 26)  // OK:创建新实例

函数式更新

scala
// 使用 copy 方法更新案例类
case class User(name: String, email: String, age: Int)

val user = User("Alice", "alice@example.com", 25)
val updatedUser = user.copy(age = 26)
val renamedUser = user.copy(name = "Alicia")

// 链式更新
val fullyUpdated = user
  .copy(name = "Alicia")
  .copy(age = 26)
  .copy(email = "alicia@example.com")

变量的最佳实践

命名约定

scala
// 好的命名
val userName = "alice"           // 驼峰命名
val maxRetryCount = 3           // 清晰的含义
val isAuthenticated = true      // 布尔值用 is/has 前缀

// 常量使用大写
val MAX_CONNECTIONS = 100
val DEFAULT_TIMEOUT = 30

// 避免的命名
val x = "alice"                 // 不清晰
val data = List(1, 2, 3)       // 太泛化
val flag = true                 // 不明确

作用域最小化

scala
// 好的做法:最小作用域
def processData(input: List[Int]): List[Int] = {
  val threshold = 10  // 只在需要的地方声明
  
  input.filter { value =>
    val adjusted = value * 2  // 在最小作用域内声明
    adjusted > threshold
  }
}

// 避免:过大的作用域
def processDataBad(input: List[Int]): List[Int] = {
  val threshold = 10
  val adjusted = 0  // 不必要的提前声明
  // ... 很多代码
  input.filter(_ * 2 > threshold)
}

使用 Option 代替 null

scala
// 好的做法
def findUser(id: Int): Option[User] = {
  if (id > 0) Some(User(s"User$id", s"user$id@example.com", 25))
  else None
}

val user = findUser(1)
val userName = user.map(_.name).getOrElse("Unknown")

// 避免使用 null
def findUserBad(id: Int): User = {
  if (id > 0) User(s"User$id", s"user$id@example.com", 25)
  else null  // 不好的做法
}

实际应用示例

配置管理

scala
object Configuration {
  // 不可变配置
  val appName: String = "MyScalaApp"
  val version: String = "1.0.0"
  
  // 从环境变量读取配置
  lazy val port: Int = sys.env.get("PORT").map(_.toInt).getOrElse(8080)
  lazy val host: String = sys.env.getOrElse("HOST", "localhost")
  lazy val dbUrl: String = sys.env.getOrElse("DB_URL", "jdbc:h2:mem:test")
  
  // 复合配置
  lazy val serverConfig = ServerConfig(host, port)
  lazy val dbConfig = DatabaseConfig(dbUrl, "user", "password")
}

case class ServerConfig(host: String, port: Int)
case class DatabaseConfig(url: String, username: String, password: String)

状态管理

scala
class Counter {
  private var _count: Int = 0
  
  def count: Int = _count  // 只读访问
  
  def increment(): Unit = {
    _count += 1
  }
  
  def decrement(): Unit = {
    _count -= 1
  }
  
  def reset(): Unit = {
    _count = 0
  }
}

// 不可变版本
case class ImmutableCounter(count: Int = 0) {
  def increment: ImmutableCounter = copy(count = count + 1)
  def decrement: ImmutableCounter = copy(count = count - 1)
  def reset: ImmutableCounter = copy(count = 0)
}

练习

练习 1:变量声明和使用

scala
object VariableExercise {
  def main(args: Array[String]): Unit = {
    // 1. 声明不可变变量
    val firstName = "John"
    val lastName = "Doe"
    val age = 30
    
    // 2. 声明可变变量
    var score = 0
    var level = 1
    
    // 3. 使用字符串插值
    val fullName = s"$firstName $lastName"
    println(s"Player: $fullName, Age: $age")
    
    // 4. 修改可变变量
    score += 100
    level += 1
    
    println(s"Score: $score, Level: $level")
    
    // 5. 使用 lazy val
    lazy val expensiveCalculation = {
      println("Performing expensive calculation...")
      (1 to 1000000).sum
    }
    
    println("Before accessing lazy val")
    println(s"Result: $expensiveCalculation")
    println(s"Result again: $expensiveCalculation")
  }
}

练习 2:模式匹配和解构

scala
object PatternMatchingExercise {
  case class Student(name: String, grades: List[Int], major: String)
  
  def analyzeStudent(student: Student): Unit = {
    // 解构案例类
    val Student(name, grades, major) = student
    
    println(s"Analyzing student: $name")
    println(s"Major: $major")
    
    // 解构列表
    grades match {
      case Nil => println("No grades available")
      case head :: Nil => println(s"Only one grade: $head")
      case first :: second :: rest => 
        println(s"First grade: $first, Second grade: $second")
        println(s"Remaining grades: ${rest.mkString(", ")}")
    }
    
    // 计算平均分
    val average = if (grades.nonEmpty) grades.sum.toDouble / grades.length else 0.0
    println(f"Average grade: $average%.2f")
  }
  
  def main(args: Array[String]): Unit = {
    val students = List(
      Student("Alice", List(85, 92, 78, 96), "Computer Science"),
      Student("Bob", List(88, 91), "Mathematics"),
      Student("Charlie", List(), "Physics")
    )
    
    students.foreach(analyzeStudent)
  }
}

总结

本章详细介绍了 Scala 变量的核心概念:

  • 变量类型val(不可变)vs var(可变)
  • 初始化方式:立即初始化、延迟初始化、条件初始化
  • 作用域规则:局部变量、成员变量、变量遮蔽
  • 类型推断:自动类型推断和显式类型声明
  • 模式匹配:解构赋值和模式匹配
  • 最佳实践:不变性、命名约定、作用域控制

理解变量的正确使用是编写高质量 Scala 代码的基础。在下一章中,我们将学习 Scala 访问修饰符,了解如何控制代码的访问权限。

本站内容仅供学习和研究使用。