Classes in Scala

Scala basically implements everything also known from Java. There are classes, nested classes, public and private classes (and members) and Traits, which resemble Java interfaces, just with more options.

What Scala does not have are static members, static inner classes and static class methods. For that, Scala uses so-called companion objects (see below). Also, Scala does not limit the number of classes or traits you can have in a single file. Multiple classes or traits (even public ones) are entirely possible and not uncommon in Scala source code. It is still good practice to put important classes into their own files. Also, all sealed classes or traits can only be extended from another class or trait in the same file.

Objects

An object implements a singleton class. Only one instance can exist of such a class and it’s automatically constructed on first access. Objects are ideal to define static methods or typical singleton objects like application contexts, configuration objects or similar thing. Object initialization is thread-safe by default, so there is no need to care about synchronizing stuff. The first thread that accesses the object will initialize it while others have to wait.

Companion Objects

A companion object can be used to implement static content for a given class. Static constants, methods and members are typically moved to the companion object. Usually, the companion object is defined in the same file, because Scala allows multiple public classes or objects in a single file. Several design patterns like the Builder can make use of companion objects to simulate Java features like static inner classes to implement a builder class.

A companion object must have the same name as the class for which it serves as companion. See the example below.

Constructors

In Scala, there is no need to define an explicit constructor. The class body itself including the class definition works as the primary constructor.

class Employee(var m_name: String = "John", val m_age: Int = 20,
  val m_gender: Employee.Gender = Employee.Gender.MALE, val m_position: String = "none"):

  private val salary: Float = 0
  /** constructor code ends here */
  def Greet(): String =
    s"I am a $m_gender Person. My name is $m_name and I am $m_age years old. My position within the company is $m_position."
  end Greet
end Employee

/** the companion object defines static stuff */
object Employee:
  enum Gender:
    case MALE, FEMALE, NONBINARY
  end Gender
end Employee

A typical class definition. The constructor defines all member variables with default values and gives them public access. For private member variables, one would simply omit the val or var keyword. The val keyword makes them immutable, such members cannot be changed for an existing instance. The variable salary is declared and also part of the constructor, in fact the entire class body is seen as the primary constructor in a Scala class. Since Scala allows named arguments with default values, constructing an object could be as easy as:

val e = Employee(m_name = "Kevin", m_age = 28) // note you can omit the new keyword in most cases

All other values would take their defaults. That’s why Scala makes the builder pattern essentially obsolete, because configuring a class at construction is very easy with named arguments.

Private constructors

Adding the keyword private to the class constructor makes it private. Such a class can only be constructed from companion objects. This is often used in Scala to implement certain patterns like the Builder or Factory when direct construction is not wanted.

class private Employee(var m_name: String = "John", val m_age: Int = 20,
  val m_gender: Employee.Gender = Employee.Gender.MALE, val m_position: String = "none"):

Alternate constructors

Like Java, Scala allows to have multiple constructors. While the primary constructor is part of the class definition, alternate constructors are defined as simple methods with the name this.

class Employee(var m_name: String = "John", val m_age: Int = 20,
  val m_gender: Employee.Gender = Employee.Gender.MALE, val m_position: String = "none"):

  def this(...)

Getters and setters

class Person(...)
  private var m_age: Int = 1

  def age = m_age
  def age_= (n_age: Int) = m_age = n_age

This defines a getter and setter for the private member m_age. The important convention for the setter is that there is NO WHITESPACE allowed after the _. The = sign must follow immediately. More on that rather strange conventions below, it all makes sense once you understand it.

A more verbose definition is seen in the following fragment. The inline keyword is optional, because the compiler will always inline such simple methods. Types for the getter (Int) and the setter (Unit) can be inferred, but it does not hurt to specify them explicitly. It can lead to better readability in your code. The return keyword in the getter is also optional as we know. Scala automatically returns the value of the last expression in a method. The same applies to the curly braces around the method body in the setter method. They can be omitted when a method body contains only a single expression or statement.

  /** simple getter and setter for m_age */
  inline def age: Int = return m_age
  inline def age_= (m_age: Int): Unit = { this.m_age = m_age }
// using getters and setters
val p = Person(...)
p.age_ = (25)
p.age  = 25   // same (see below)

The getter is quite obvious and simple to understand. It returns the member m_age as Int. The setter is a bit more obscure. The underscore in age_ is basically a placeholder for a space character and the compiler interprets the complete name for the setter as age_= making the = sign part of the name. That’s why you cannot place a space between the _ and the =. Method name definitions are not allowed to contain spaces. That’s also why you have to call it as age_= (25). You DO NOT assign 25 to the member, you call the method age_= with 25 as its argument. Since Scala allows you to replace underscores with spaces and leave out parenthesis around method parameters, you can also write age = 25. So, for short, p.age = 25 is absolutely the same as p.age_=(25). The difference is just syntax sugar provided by the Scala compiler.

Finally, you can get rid of the inline because the compiler will do it for you. Getters and setters are a good example for Scala’s syntax flexibility, but you need to understand it, otherwise it can lead to some confusion.

Tags: scala