Skip to content

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              // OK

Choosing 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 initialization

Lazy 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 directly

Conditional 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 method

Type 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 None

Cases 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 version

Variable 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 = 4

Partial 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 instance

Functional 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 explicit

Minimizing 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) vs var (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.

Content is for learning and research only.