#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
// 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
// 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 // OK#Choosing val vs var
// 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
val immediate = 42 // Immediate initialization
var mutable = "initial" // Immediate initialization#Lazy Initialization
// 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 directly#Conditional Initialization
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
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
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
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 method#Type Inference
#Basic Type Inference
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
// 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 None#Cases Requiring Explicit Types
// 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 version#Variable Pattern Matching
#Destructuring Assignment
// 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 = 4#Partial Destructuring
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
// 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 instance#Functional Update
// 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
// 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 explicit#Minimizing Scope
// 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
// 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
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
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
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
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.