Objects: apply Methods in Companion Objects

In Scala, you can also use companion objects to create constructors for a class. Technically they’re not constructors, but in the end they work just like constructors. If you’ve heard of the Factory Method in OOP, putting this type of code in an object is similar to that. Other languages and tools refer to this as creating “builders.”

The process

The way you do this is you create one or more methods named apply in the companion object. Each apply method creates a different way that instances of your class can be created.

For example, assuming that you want to create constructors for a Person class:

  1. Define a Person class and Person object in the same file
  2. Make the Person class constructor private
  3. Define one or more apply methods in the object to serve as factory methods (which work just like constructors)

Example 1

For Steps 1 and 2, create the class and object in the same file, and make the Person class constructor private:

// note the 'private' keyword here
class Person private(val name: String):
    // define any instance members you need here

object Person:
    // define any static members you need here

Then for Step 3, create one or more apply methods in the companion object:

class Person private(val name: String):
    override def toString = name

object Person:
    // the “constructor”
    def apply(theName: String): Person = new Person(theName)

Given this code, you can now create new Person instances as shown in these examples:

val bert = Person("Bert")
val a = List(Person("Bert"), Person("Ernie"))

In Scala 2 a benefit of this approach was that it eliminated the need for the new keyword when creating new instances of a class. But because new isn’t needed in most situations in Scala 3, you may want to use this technique because you prefer this factory approach. In fact, I’ve seen that several experienced developers like to create their class “constructors” this way. Also note that the new keyword is required in the apply method to create a new Person instance.

How this works

The way this works is that an apply method defined in a companion object is treated specially by the Scala compiler. Essentially, there’s a little syntactic sugar baked into the compiler so that when it sees this code:

val p = Person("Fred Flintstone")

one of the first things it does is to look around and see if it can find an apply method in a companion object. If it does find a matching apply method in a companion object — i.e., an apply method that takes one String parameter — the compiler turns that code into this code:

val p = Person.apply("Fred Flintstone")

You can demonstrate this for yourself by putting this code into the Scala REPL.

Because of this, apply is often referred to as a factory method or perhaps a “builder.” Technically it’s not a constructor, but it works just like one.

Example 2: More factory methods!

When you want to use this technique to provide multiple ways to build a class, define multiple apply methods with the desired signatures, as shown in this OOP example:

class Person private(var name: String, var age: Int):
    override def toString = s"$name is $age years old"

object Person:
    // three ways to build a Person
    def apply(): Person = new Person("", 0)
    def apply(name: String): Person = new Person(name, 0)
    def apply(name: String, age: Int): Person = new Person(name, age)

That code creates three ways to build new Person instances:

println(Person())               //  is 0 years old
println(Person("Bert"))         // Bert is 0 years old
println(Person("Ernie", 22))    // Ernie is 22 years old

This text comes from my book, Learn Scala 3 The Fast Way!.