Scala Variables
Variables are the basic units for storing data in programs. This chapter will detail variable declaration, initialization, and usage in Scala.
Variable Declaration
Scala provides two ways to declare variables: val (immutable) and var (mutable).
val - Immutable Variables
scala
// Basic declaration
val name = "Alice" // Type inferred as String
val age = 25 // Type inferred as Int
val height = 5.6 // Type inferred as Double
// Explicit type declaration
val count: Int = 10
val message: String = "Hello, Scala!"
val isActive: Boolean = true
// Once assigned, cannot be modified again
// name = "Bob" // Compilation error!var - Mutable Variables
scala
// Basic declaration
var counter = 0 // Type inferred as Int
var status = "pending" // Type inferred as String
// Explicit type declaration
var score: Double = 0.0
var items: List[String] = List.empty
// Can be reassigned
counter = counter + 1 // OK
status = "completed" // OK
score = 95.5 // OKChoosing val vs var
scala
// Recommended: Use val preferentially
val configuration = Map(
"host" -> "localhost",
"port" -> 8080,
"timeout" -> 30
)
// Only use var when necessary
var attempts = 0
while (attempts < 3) {
// Try to connect
attempts += 1
}
// Functional style alternative to var
val results = (1 to 3).map { attempt =>
// Try to connect and return result
s"Attempt $attempt"
}Variable Initialization
Immediate Initialization
scala
val immediate = 42 // Immediate initialization
var mutable = "initial" // Immediate initializationLazy Initialization
scala
// lazy val - Lazy initialization, only computed on first access
lazy val expensiveComputation = {
println("Computing...")
Thread.sleep(1000) // Simulate expensive operation
42 * 42
}
println("Before access")
println(expensiveComputation) // Computation executes here
println(expensiveComputation) // Returns cached result directlyConditional Initialization
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")
}
// Use Option to handle possibly non-existent values
val maybePort: Option[Int] = sys.env.get("PORT").map(_.toInt)
val port = maybePort.getOrElse(8080)Scope and Lifecycle
Local Variables
scala
def example(): Unit = {
val localVar = "local" // Method scope
if (true) {
val blockVar = "block" // Block scope
println(localVar) // Can access outer variable
println(blockVar) // Can access current block variable
}
// println(blockVar) // Compilation error: out of scope
}Variable Shadowing
scala
val x = 10 // Outer variable
def shadowExample(): Unit = {
val x = 20 // Shadows outer variable
println(x) // Outputs 20
{
val x = 30 // Further shadowing
println(x) // Outputs 30
}
println(x) // Outputs 20
}Class Member Variables
scala
class Person(val name: String, var age: Int) {
// Primary constructor parameters automatically become member variables
private val id = java.util.UUID.randomUUID() // Private member
protected var status = "active" // Protected member
def birthday(): Unit = {
age += 1 // Modify mutable member variable
}
def getId: String = id.toString // Access private member
}
val person = new Person("Alice", 25)
println(person.name) // Access val member
person.age = 26 // Modify var member
person.birthday() // Modify through methodType Inference
Basic Type Inference
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
// Collection type inference
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]Complex Type Inference
scala
// Function type inference
val add = (x: Int, y: Int) => x + y // (Int, Int) => Int
val square = (x: Int) => x * x // Int => Int
// Higher-order function type inference
val numbers = List(1, 2, 3, 4, 5)
val doubled = numbers.map(_ * 2) // List[Int]
val filtered = numbers.filter(_ > 2) // List[Int]
// Option type inference
val some = Some(42) // Some[Int]
val none = None // None.type
val option: Option[Int] = if (true) Some(42) else NoneCases Requiring Explicit Types
scala
// Empty collections need type annotation
val emptyList: List[String] = List()
val emptyMap: Map[String, Int] = Map()
// Recursive functions need return type
def factorial(n: Int): Int = {
if (n <= 1) 1 else n * factorial(n - 1)
}
// Overloaded method calls may need type annotation
def process(x: Int): String = s"Int: $x"
def process(x: String): String = s"String: $x"
val result: String = process(42) // Explicitly call Int versionVariable Pattern Matching
Destructuring Assignment
scala
// Tuple destructuring
val tuple = ("Alice", 25, "Engineer")
val (name, age, profession) = tuple
// List destructuring
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)
// Array destructuring
val Array(a, b, c) = Array(1, 2, 3)
// Case class destructuring
case class Point(x: Int, y: Int)
val point = Point(3, 4)
val Point(x, y) = point // x = 3, y = 4Partial Destructuring
scala
val numbers = List(1, 2, 3, 4, 5)
// Only take first two elements
val first :: second :: _ = numbers
// Use wildcard to ignore unwanted parts
val (name, _, profession) = ("Alice", 25, "Engineer")
// Option destructuring
val maybeValue: Option[String] = Some("hello")
val Some(value) = maybeValue // Dangerous: throws exception if None
// Safe Option handling
maybeValue match {
case Some(v) => println(s"Value: $v")
case None => println("No value")
}Variable Immutability
Deep vs Shallow Immutability
scala
// val only guarantees reference immutability, not content immutability
val mutableList = scala.collection.mutable.ListBuffer(1, 2, 3)
// mutableList = scala.collection.mutable.ListBuffer(4, 5, 6) // Error
mutableList += 4 // OK: Modify content
// True immutability
val immutableList = List(1, 2, 3)
// immutableList = List(4, 5, 6) // Error
val newList = immutableList :+ 4 // OK: Create new list
// Immutable case class
case class Person(name: String, age: Int)
val person = Person("Alice", 25)
// person.age = 26 // Error: case class fields default to val
val olderPerson = person.copy(age = 26) // OK: Create new instanceFunctional Update
scala
// Use copy method to update case class
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")
// Chained updates
val fullyUpdated = user
.copy(name = "Alicia")
.copy(age = 26)
.copy(email = "alicia@example.com")Best Practices for Variables
Naming Conventions
scala
// Good naming
val userName = "alice" // camelCase
val maxRetryCount = 3 // Clear meaning
val isAuthenticated = true // Booleans use is/has prefix
// Constants use uppercase
val MAX_CONNECTIONS = 100
val DEFAULT_TIMEOUT = 30
// Avoid naming
val x = "alice" // Not clear
val data = List(1, 2, 3) // Too generic
val flag = true // Not explicitMinimizing Scope
scala
// Good practice: minimal scope
def processData(input: List[Int]): List[Int] = {
val threshold = 10 // Declare only where needed
input.filter { value =>
val adjusted = value * 2 // Declare within minimal scope
adjusted > threshold
}
}
// Avoid: excessive scope
def processDataBad(input: List[Int]): List[Int] = {
val threshold = 10
val adjusted = 0 // Unnecessary early declaration
// ... lots of code
input.filter(_ * 2 > threshold)
}Use Option Instead of null
scala
// Good practice
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")
// Avoid using null
def findUserBad(id: Int): User = {
if (id > 0) User(s"User$id", s"user$id@example.com", 25)
else null // Bad practice
}Practical Application Examples
Configuration Management
scala
object Configuration {
// Immutable configuration
val appName: String = "MyScalaApp"
val version: String = "1.0.0"
// Read configuration from environment variables
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")
// Composite configuration
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)State Management
scala
class Counter {
private var _count: Int = 0
def count: Int = _count // Read-only access
def increment(): Unit = {
_count += 1
}
def decrement(): Unit = {
_count -= 1
}
def reset(): Unit = {
_count = 0
}
}
// Immutable version
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)
}Exercises
Exercise 1: Variable Declaration and Usage
scala
object VariableExercise {
def main(args: Array[String]): Unit = {
// 1. Declare immutable variables
val firstName = "John"
val lastName = "Doe"
val age = 30
// 2. Declare mutable variables
var score = 0
var level = 1
// 3. Use string interpolation
val fullName = s"$firstName $lastName"
println(s"Player: $fullName, Age: $age")
// 4. Modify mutable variables
score += 100
level += 1
println(s"Score: $score, Level: $level")
// 5. Use 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")
}
}Exercise 2: Pattern Matching and Destructuring
scala
object PatternMatchingExercise {
case class Student(name: String, grades: List[Int], major: String)
def analyzeStudent(student: Student): Unit = {
// Destructure case class
val Student(name, grades, major) = student
println(s"Analyzing student: $name")
println(s"Major: $major")
// Destructure list
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(", ")}")
}
// Calculate average
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)
}
}Summary
This chapter detailed the core concepts of Scala variables:
- Variable Types:
val(immutable) vsvar(mutable) - Initialization Methods: Immediate, lazy, and conditional initialization
- Scope Rules: Local variables, member variables, variable shadowing
- Type Inference: Automatic type inference and explicit type declarations
- Pattern Matching: Destructuring assignment and pattern matching
- Best Practices: Immutability, naming conventions, scope control
Understanding the correct use of variables is the foundation for writing high-quality Scala code. In the next chapter, we will learn Scala Access Modifiers to understand how to control code access permissions.