val, var, and lazy val: Understanding Immutability
Introduction
One of the most important concepts to understand in Scala is the distinction between mutable and immutable data. Unlike many programming languages where variables are mutable by default, Scala encourages immutability as the preferred approach.
In this lesson, we'll explore the three ways to declare values in Scala: val
, var
, and lazy val
. Understanding when and how to use each one is crucial for writing idiomatic Scala code.
val: Immutable Values
The val
keyword creates an immutable value – once assigned, it cannot be changed. Think of it as a constant.
val name = "Alice"
val age = 25
val pi = 3.14159
// This would cause a compilation error:
// name = "Bob" // Error: reassignment to val
Type Inference with val
Scala's type inference is powerful. You can explicitly specify types, but it's often unnecessary:
// Explicit type annotation
val message: String = "Hello, World!"
val count: Int = 42
val price: Double = 19.99
// Type inference (preferred when obvious)
val message = "Hello, World!" // String inferred
val count = 42 // Int inferred
val price = 19.99 // Double inferred
Immutable Collections
Even collections can be immutable with val
:
val numbers = List(1, 2, 3, 4, 5)
val fruits = Set("apple", "banana", "orange")
val scores = Map("Alice" -> 95, "Bob" -> 87)
// You cannot reassign the collection itself:
// numbers = List(6, 7, 8) // Error!
// But you can create new collections:
val moreNumbers = numbers :+ 6 // Creates new list [1, 2, 3, 4, 5, 6]
val moreFruits = fruits + "grape" // Creates new set
Benefits of Immutability
- Thread Safety: Immutable values can be safely shared between threads
- Easier Reasoning: You know the value won't change unexpectedly
- Functional Programming: Enables powerful functional programming techniques
- Debugging: Fewer surprises when debugging code
var: Mutable Variables
The var
keyword creates a mutable variable that can be reassigned:
var counter = 0
var name = "Initial"
var isActive = true
// These are all valid:
counter = counter + 1 // or counter += 1
name = "Updated"
isActive = false
println(s"Counter: $counter, Name: $name, Active: $isActive")
// Output: Counter: 1, Name: Updated, Active: false
When to Use var
Use var
sparingly and only when you truly need mutability:
// Example: Accumulating a result in a loop
var sum = 0
for (i <- 1 to 10) {
sum += i
}
println(s"Sum: $sum") // Sum: 55
// Example: Reading user input until a condition is met
import scala.io.StdIn
var input = ""
while (input != "quit") {
print("Enter command (or 'quit' to exit): ")
input = StdIn.readLine()
if (input != "quit") {
println(s"You entered: $input")
}
}
Functional Alternatives to var
Often, you can replace var
with functional approaches:
// Instead of using var in a loop:
var sum = 0
for (i <- 1 to 10) {
sum += i
}
// Use functional methods:
val sum = (1 to 10).sum
// Instead of building a list with var:
var result = List.empty[Int]
for (i <- 1 to 5) {
result = result :+ (i * 2)
}
// Use map:
val result = (1 to 5).map(_ * 2).toList
lazy val: Deferred Evaluation
The lazy val
keyword creates a value that is computed only when first accessed. This is called "lazy evaluation" or "call-by-need."
lazy val expensiveComputation = {
println("Computing...")
Thread.sleep(1000) // Simulate expensive operation
42
}
println("Before accessing lazy val")
println(s"Value: $expensiveComputation") // "Computing..." printed here
println(s"Value again: $expensiveComputation") // No "Computing..." this time
Comments
Be the first to comment on this lesson!