How to create Scala object instances without using the “new” keyword (apply)

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 6.8, “How to create object instances without using the ‘new’ keyword.”

Update!

This text was written for Scala 2. Things have changed a bit in Scala 3, and this approach is generally NOT NEEDED in Scala 3.

Problem

You’ve seen that Scala code looks cleaner when you don’t always have to use the new keyword to create a new instance of a class, like this:

val a = Array(
    Person("John"),
    Person("Paul")
)

So you want to know how to write your code to make your classes work like this.

Solution

There are two ways to do this:

  • Create a companion object for your class, and define an apply method in the companion object with the desired constructor signature.
  • Define your class as a “case class.”

You’ll look at both approaches next.

Creating a companion object with an apply method

To demonstrate the first approach, define a Person class and Person object in the same file. Define an apply method in the object that takes the desired parameters. This method is essentially the constructor of your class:

class Person {
    var name: String = _
}

object Person {
    def apply(name: String): Person = {
        var p = new Person
        p.name = name
        p
    }
}

Given this definition, you can create new Person instances without using the new keyword, as shown in these examples:

val dawn = Person("Dawn")
val a = Array(Person("Dawn"), Person("Elijah"))

The apply method in a companion object is treated specially by the Scala compiler and lets you create new instances of your class without requiring the new keyword. (More on this in the Discussion.)

Declare your class as a “case class”

The second solution to the problem is to declare your class as a case class, defining it with the desired constructor:

case class Person (var name: String)

This approach also lets you create new class instances without requiring the new keyword:

val p = Person("Fred Flinstone")

With case classes, this works because the case class generates an apply method in a companion object for you. However, it’s important to know that a case class creates much more code for you than just the apply method. This is discussed in depth in the Discussion.

Discussion

An apply method defined in the companion object of a class is treated specially by the Scala compiler. There is essentially a little syntactic sugar baked into Scala that converts this code:

val p = Person("Fred Flinstone")

into this code:

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

The apply method is basically a factory method, and Scala’s little bit of syntactic sugar lets you use the syntax shown, creating new class instances without using the new keyword.

Providing multiple constructors with additional apply methods

To create multiple constructors when manually defining your own apply method, just define multiple apply methods in the companion object that provide the constructor signatures you want:

class Person {
    var name = ""
    var age = 0
}

object Person {

    // a one-arg constructor
    def apply(name: String): Person = {
        var p = new Person
        p.name = name
        p
    }

    // a two-arg constructor
    def apply(name: String, age: Int): Person = {
        var p = new Person
        p.name = name
        p.age = age
        p
    }

}

You can now create a new Person instance in these ways:

val fred = Person("Fred")
val john = Person("John", 42)

I’m using the term “constructor” loosely here, but each apply method does define a different way to construct an instance.

Providing multiple constructors for case classes

To provide multiple constructors for a case class, it’s important to know what the case class declaration actually does.

If you look at the code the Scala compiler generates for the case class example, you’ll see that see it creates two output files, Person$.class and Person.class. If you disassemble Person$.class with the javap command, you’ll see that it contains an apply method, along with many others:

$ javap Person$

Compiled from "Person.scala"
public final class Person$ extends scala.runtime.AbstractFunction1 
implements scala.ScalaObject,scala.Serializable{
    public static final Person$ MODULE$;
    public static {};
    public final java.lang.String toString();
    public scala.Option unapply(Person);
    public Person apply(java.lang.String); // the apply method (returns a Person)
    public java.lang.Object readResolve();
    public java.lang.Object apply(java.lang.Object);
}

You can also disassemble Person.class to see what it contains. For a simple class like this, it contains an additional 20 methods; because all of this extra code isn’t needed for some purposes, some developers don’t like case classes.

See Recipe 4.14, “Generating Boilerplate Code with Case Classes”, for a thorough discussion of what code is generated for case classes, and why.

Note that the apply method in the disassembled code accepts one String argument:

public Person apply(java.lang.String);

That String corresponds to the name field in your case class constructor:

case class Person (var name: String)

So, it’s important to know that when a case class is created, it writes the accessor and (optional) mutator methods only for the default constructor. As a result, (a) it’s best to define all class parameters in the default constructor, and (b) write apply methods for the auxiliary constructors you want.

This is demonstrated in the following code, which I place in a file named Person.scala:

// want accessor and mutator methods for the name and age fields
case class Person (var name: String, var age: Int)

// define two auxiliary constructors
object Person {
    def apply() = new Person("<no name>", 0)
    def apply(name: String) = new Person(name, 0)
}

Because name and age are declared as var fields, accessor and mutator methods will both be generated. Also, two apply methods are declared in the object: a no-args constructor, and a one-arg constructor.

As a result, you can create instances of your class in three different ways, as demonstrated in the following code:

object Test extends App {

    val a = Person()
    val b = Person("Al")
    val c = Person("William Shatner", 82)

    println(a)
    println(b)
    println(c)

    // test the mutator methods
    a.name = "Leonard Nimoy"
    a.age = 82
    println(a)

}

Running this test object results in the following output:

Person(<no name>,0)
Person(Al,0)
Person(William Shatner,82)
Person(Leonard Nimoy,82)

For more information on case classes, see Recipe 4.14, “Generating Boilerplate Code with Scala Case Classes”.