Scala simple class design
Categories:
Designing Simple Classes in Scala: A Practical Guide

Learn the fundamentals of creating clean, effective, and idiomatic classes in Scala, covering primary constructors, immutability, and basic methods.
Scala, a powerful language that blends object-oriented and functional programming paradigms, offers a concise and expressive way to define classes. Understanding how to design simple, effective classes is fundamental to writing maintainable and scalable Scala applications. This article will guide you through the core concepts of Scala class design, focusing on primary constructors, immutability, and basic method definitions.
The Primary Constructor: Your Class's Blueprint
In Scala, the primary constructor is an integral part of the class definition itself. Unlike Java, where constructors are separate methods, Scala's primary constructor parameters are declared directly after the class name. These parameters can be used to define both constructor arguments and class fields, depending on how they are declared. By default, parameters are private val
s, meaning they are immutable and accessible only within the class. To make them public fields, you must explicitly declare them with val
or var
keywords.
class Person(val name: String, var age: Int) {
// 'name' is a public immutable field
// 'age' is a public mutable field
// A method that uses the constructor parameters
def greet(): String = s"Hello, my name is $name and I am $age years old."
}
// Usage:
val alice = new Person("Alice", 30)
println(alice.name) // Output: Alice
println(alice.age) // Output: 30
alice.age = 31 // Allowed because 'age' is a var
println(alice.greet()) // Output: Hello, my name is Alice and I am 31 years old.
Defining a simple Scala class with a primary constructor and methods.
val
over var
for constructor parameters and class fields whenever possible. Immutability leads to more predictable and easier-to-reason-about code, especially in concurrent environments.Immutability and Case Classes
Scala strongly encourages immutability. An immutable object's state cannot be changed after it's created. This simplifies reasoning about code, reduces side effects, and makes concurrent programming safer. While you can create immutable classes manually using val
fields, Scala provides a special type of class called a case class
that automatically provides many benefits for immutable data structures:
- Constructor parameters become public
val
s by default. equals
andhashCode
methods are automatically generated. This means two case class instances are considered equal if their constructor parameters are equal.toString
method is automatically generated.copy
method is automatically generated. This allows you to create new instances with modified fields without altering the original.- Pattern matching support. Case classes are ideal for pattern matching, a powerful feature in Scala.
case class Point(x: Int, y: Int)
val p1 = Point(1, 2)
val p2 = Point(1, 2)
val p3 = Point(3, 4)
println(p1 == p2) // Output: true (structural equality)
println(p1 == p3) // Output: false
// Using the copy method to create a new instance with a modified field
val p1Moved = p1.copy(x = 5)
println(p1Moved) // Output: Point(5,2)
println(p1) // Output: Point(1,2) (original is unchanged)
Demonstrating the benefits of Scala case classes for immutable data.
classDiagram class Person { +String name +Int age +greet(): String } class Point { +Int x +Int y } Person "1" -- "*" Point : has visited note for Person "name is val, age is var" note for Point "Case class: all fields are val by default"
UML class diagram illustrating a simple Person
class and a Point
case class.
Adding Behavior: Methods and Auxiliary Constructors
Classes aren't just for holding data; they also define behavior through methods. Methods are defined using the def
keyword. While the primary constructor is sufficient for most cases, Scala also supports auxiliary constructors, defined using def this(...)
. These are useful when you need to provide multiple ways to construct an object, but they must always call the primary constructor (or another auxiliary constructor that eventually calls the primary one) as their first action.
class Rectangle(val width: Double, val height: Double) {
// Primary constructor
// Auxiliary constructor for a square
def this(side: Double) = this(side, side)
def area(): Double = width * height
def perimeter(): Double = 2 * (width + height)
}
// Usage:
val rect = new Rectangle(10.0, 5.0)
println(s"Rectangle area: ${rect.area()}") // Output: Rectangle area: 50.0
val square = new Rectangle(7.0)
println(s"Square perimeter: ${square.perimeter()}") // Output: Square perimeter: 28.0
Class with a primary and an auxiliary constructor, and methods.