1. Introduction

Scala is a hybrid between functional and object-oriented programming, and it smoothly integrates the features of object-oriented and functional languages.

In this tutorial, we’ll learn how to implement object-oriented programming concepts in Scala such as defining classes, encapsulation, inheritance, and polymorphism.

2. Classes and Objects

2.1. Defining Classes

In Scala, we can define a class with the class keyword:

class Bread

After the class is defined, we can create an object of that class with the new keyword:

val whiteBread = new Bread

2.2. Defining Fields

To make a class more meaningful, we can add fields to the class. We can define a field in a class using var or val, like defining a variable:

class Bread {
  val name: String = "white"
  var weight: Int = 1
}

And then we can access the fields directly from the object we created:

val whiteBread = new Bread
assert(whiteBread.name === "white")
assert(whiteBread.weight === 1)

We can modify the weight as it’s a mutable object:

whiteBread.weight = 2
assert(whiteBread.weight === 2)

2.3. Defining Constructors

A constructor is used to assign values in a class to be used later in the fields or methods of the class. We can also assign a default value in the constructor definition:

class Bread(breadName: String = "white") {
  val name: String = breadName
}

Now, let’s see what happens if we assign a value to the constructor:

val grainBread = new Bread("grain")
assert(grainBread.name === "grain")

2.4. Defining Methods

We can define a method in a class using the def keyword. Let’s try adding a getPrice method to the Bread class to calculate the price based on the bread weight and price unit:

class Bread(breadName: String = "white", breadWeight: Int = 1) {
  val name: String = breadName
  var weight: Int = breadWeight

  def getPrice(priceUnit: Int): Int = {
    priceUnit * weight
  }
}

There’s no need to specify the return keyword, as the return value is the result of the last instruction in the method. We can call the method after we create an object for the class:

val bread = new Bread("grain", 2)
assert(bread.getPrice(2) === 4)

3. Encapsulation

Encapsulation is one of the fundamental concepts in object-oriented programming. It describes the idea of bundling data and methods that work on that data within a class.

Let’s consider a class that represents a Sandwich:

import scala.collection.mutable.ArrayBuffer

class Sandwich(bread: Bread, filling: ArrayBuffer[String]) {
  private def getFillingsName: String = {
    filling.mkString(", ")
  }
}

In the class definition, all properties and methods are private and we cannot access them from the object we created. Doing so will result in a compilation error:

val sandwich = new Sandwich(new Bread("white"), ArrayBuffer("strawberry jam", "chocolate"))
sandwich.bread // error: value bread is not a member of Sandwich
sandwich.filling // error: value filling is not a member of Sandwich
sandwich.getFillingsName // error: method getFillingsName in class Sandwich cannot be accessed in Sandwich

We can access or manipulate them with public methods:

def getDescription: String = {
  s"This is a sandwich with ${bread.name} bread and $getFillingsName filling"
}

def addFilling(extraFilling: String): Unit = {
  filling.append(extraFilling)
}

With the defined methods, now we can access and manipulate the private fields and methods of the class:

val sandwich = new Sandwich(new Bread("sourdough"), ArrayBuffer("chicken"))
sandwich.addFilling("lettuce")
assert(sandwich.getDescription === "This is a sandwich with sourdough bread and chicken, lettuce filling")

4. Inheritance

Inheritance is a mechanism that allows deriving a class from another, thus enabling the deriving class to reuse fields and methods from the derived one. The class that reuses the fields and methods is called the subclass while the class that provides them is called the superclass. The subclass will inherit all the non-private fields and methods defined in the superclass.

To create a class based on another, we make use of the extends keyword. Let’s define a base class Vehicle to be inherited first:

class Vehicle(val numWheels: Int, val color: String) {
  def accelerate(): Unit = { println("Vroom Vroom" }
}

Then, we can define another class, Bicycle, that inherits the class Vehicle:

class Bicycle(bikeColor: String, val bikeType: String) extends Vehicle(2, bikeColor) {
  def maxSpeed(): Int = {
    bikeType match {
      case "road" => 60
      case _ => 20
    }
  }
}

There, we can expect that Bicycle will inherit all of Vehicle‘s fields and methods, plus the additional bikeType field and maxSpeed method:

val bicycle = new Bicycle("red", "road")
bicycle.accelerate()
assert(bicycle.numWheels === 2)
assert(bicycle.color === "red")
assert(bicycle.bikeType === "road")
assert(bicycle.maxSpeed() === 60)

5. Polymorphism

Polymorphism is the ability of an OOP language to process data differently depending on their types of inputs. Here, we’ll discuss two types of polymorphism; method overloading and method overriding.

5.1. Method Overloading

Overloading a method means using a method name but applying different logic, depending on the parameters defined in the methods.

Let’s illustrate by overloading the maxSpeed method in Bicycle class so it can accept a parameter:

def maxSpeed(speedLimit: Int): Int = {
  bikeType match {
    case "road" => if(speedLimit < 60) speedLimit else 60
    case _ => if(speedLimit < 20) speedLimit else 20
  }
}

Then, we can see the difference when using either maxSpeed method:

val bicycle = new Bicycle("red", "road")
assert(bicycle.maxSpeed() === 60)
assert(bicycle.maxSpeed(10) === 10)

5.2. Method Overriding

Another type of polymorphism is method overriding. Overriding is used in a subclass to redefine the implementation of a method provided by its superclass. We can do so by adding keyword override before defining the method:

override def accelerate(): Unit = { println("Whoooosh") }

By overriding the accelerate method, calling it will now print “Whoooosh” instead of “Vroom Vroom”.

6. Conclusion

In this tutorial, we’ve explored object-oriented programming concepts and how to accomplish them in Scala.

As usual, all the examples used in this tutorial are available over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.