Classes, Objects and Constructors in Scala
In the previous chapter, we learned that classes and objects are the building blocks of object-oriented programming.
Now let's put that understanding into practice.
Imagine you're developing an employee management application. You need to maintain information about hundreds of employees. Every employee has a name, department, experience and salary, but the actual values are different.
Creating separate variables for every employee would quickly become difficult to manage. Instead, we can define a common structure and create multiple employees from it.
That's exactly what classes and objects help us achieve.
In this chapter, we'll learn how to create classes, initialize objects using constructors, pass values to objects and understand how Scala manages object initialization.
1. Creating Your First Class
A class defines the properties and behaviour that its objects will have.
Let's start with a simple Employee class.
We've defined two properties and one method. But defining a class doesn't automatically create an employee object.
class Employee:
val name = "Deepak"
val department = "IT"
def displayDetails(): Unit =
println(s"$name works in $department")To use the class, we need to create an instance.
val employee = new Employee()
employee.displayDetails()
Output:
Deepak works in IT
Here, employee
is an object created from the
Employee class.
You can think of a class as a blueprint and an object as something created using that blueprint.
2. Understanding Constructors
Let's improve our Employee class.
In the previous example, every employee would have the same name and department. That's not very useful when building a real application.
We need a way to provide employee information when creating an object.
Scala solves this using constructors.
A constructor initializes an object with the values provided during its creation.
class Employee(val name: String, val department: String)
val employee1 = new Employee("Deepak", "IT")
val employee2 = new Employee("Rahul", "Finance")
println(employee1.name)
println(employee2.name)Output:
Deepak
Rahul
Notice how we created two employees using the same class but supplied different values.
In Scala, the primary constructor is declared directly in the class definition.
class Employee(val name: String, val department: String)
The values passed during object creation are assigned to the corresponding constructor parameters.
This is one of the reasons Scala class declarations are relatively concise.
3. Understanding val, var and Constructor Parameters
When working with Scala classes, you'll frequently see constructor parameters declared using val, var, or neither.
Although the syntax looks similar, their behaviour is different.
Let's understand each one.
Using val
class Employee(val name: String)
val employee = new Employee("Deepak")
println(employee.name)The name property can be accessed from outside the class, but it cannot be reassigned.
employee.name = "Rahul" // Compilation error
Using var
Sometimes a property's value needs to change during an object's lifetime.
class Employee(var salary: Double)
val employee = new Employee(80000)
employee.salary = 95000
println(employee.salary)Output:
95000.0
Since salary is declared using var, it can be reassigned.
Without val or var
Constructor parameters don't always need to become publicly accessible properties.
class Employee(name: String):
def displayName(): Unit =
println(name)
val employee = new Employee("Deepak")
employee.displayName()The parameter name can be used inside the class, but it isn't exposed as a public field. You cannot access it using employee.name
.Here's a quick comparison:
| Declaration | Publicly accessible as a field? | Reassignable externally? |
|---|---|---|
val name | Yes | No |
var name | Yes | Yes |
name | No | No |
A useful practice: Prefer val when a property should not change. Use var only when you genuinely need to modify its value.
4. What Happens When an Object Is Created?
There is something interesting about Scala classes that is worth understanding.
When an object is created, Scala executes the class initialization statements.
Let's look at an example.
class Employee(val name: String, val salary: Double):
println(s"Creating employee: $name")
val annualSalary = salary * 12
def displayDetails(): Unit =
println(s"Name: $name")
println(s"Annual Salary: $annualSalary")
val employee = new Employee("Deepak", 80000)
employee.displayDetails()Output:
Creating employee: Deepak
Name: Deepak
Annual Salary: 960000.0
Notice that the creation message appears immediately when the object is instantiated.
We didn't call displayDetails() before that message appeared.
That's because the statements inside the class body are executed as part of object initialization.
This is useful when you need to understand how properties are initialized and in which order initialization code runs.
5. Default Constructor Parameters
In real applications, some properties have a common default value.
For example, most employees might belong to the IT department unless another department is specified.
Instead of passing the same value every time, Scala allows us to define default constructor parameters.
class Employee( val name: String, val department: String = "IT", val experience: Int = 1)
val employee1 = new Employee("Deepak")
val employee2 = new Employee("Rahul", "Finance", 8)
println(employee1.department)
println(employee2.department)Output:
IT
Finance
For employee1, we supplied only the name. Scala automatically used the default department and experience.
We can also provide only a particular optional value by using named arguments.
val employee3 = new Employee( name = "Amit", experience = 5)
Here, the department remains IT, while experience is set to 5.
Default parameters make constructors easier to use and reduce the need for multiple constructor definitions.
6. Creating Multiple Objects
One of the biggest advantages of classes is that we can create multiple independent objects from the same class.
Let's see what that means.
class Employee( val name: String, var salary: Double):
def display(): Unit =
println(s"$name earns $salary")
val employee1 = new Employee("Deepak", 80000)
val employee2 = new Employee("Rahul", 60000)
employee1.salary = 90000
employee1.display()
employee2.display()Output:
Deepak earns 90000.0
Rahul earns 60000.0
Even though both employees were created from the same class, changing one employee's salary doesn't affect the other.
Each object maintains its own instance state.
This is an important concept when developing applications that manage customers, employees, products, orders or transactions.
7. Auxiliary Constructors
Sometimes a class needs to support different ways of creating objects.
Scala provides auxiliary constructors for this purpose.
Consider a situation where an employee can be created with both a name and department, or with just a name.
class Employee(val name: String, val department: String):
def this(name: String) = this(name, "IT")
val employee1 = new Employee("Deepak", "Finance")
val employee2 = new Employee("Rahul")
println(employee1.department)
println(employee2.department)Output:
Finance
IT
Let's understand what's happening.
The primary constructor accepts
nameanddepartment.The auxiliary constructor accepts only
name.When the second constructor is used, it supplies
ITas the department.The call
this(name, "IT")delegates to the primary constructor.
Every auxiliary constructor must ultimately call the primary constructor or another auxiliary constructor that does.
When should you use it?
Use auxiliary constructors when you genuinely need different initialization signatures. For simpler optional values, default constructor parameters are often a cleaner choice.
8. Scala 2 vs Scala 3: Creating Objects
If you're learning Scala 2 and Scala 3 together, you'll notice a small difference in object creation syntax.
In Scala 2.13, creating an instance of a regular class generally requires the new keyword.
class Employee(val name: String)
val employee = new Employee("Deepak")Scala 3 allows you to omit new in ordinary constructor calls.
class Employee(val name: String)
val employee = Employee("Deepak")Both approaches create an instance of the class.
Here's a quick comparison:
| Feature | Scala 2.13 | Scala 3 |
|---|---|---|
| Class declaration | class Employee { } | class Employee: |
| Object creation | new Employee() | Employee() or new Employee() |
| Primary constructor | Supported | Supported |
| Default parameters | Supported | Supported |
| Auxiliary constructors | Supported | Supported |
Scala 3 still supports braces and the new keyword. The indentation-based syntax and omission of new are language conveniences, not requirements.
9. Where Do We Use These Concepts?
The concepts we've covered are used throughout real-world applications.
For example:
In an employee management system, constructors initialize employee details.
In an ecommerce application, product objects hold individual product information.
In a banking application, account objects maintain account-specific state.
In a payment application, transaction objects hold information about individual transactions.
The important thing is to identify what information belongs to an object and what behaviour that object should provide.
Don't create classes just to make the application look object-oriented. Create them when they help represent a meaningful concept in your application.
