This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 4.3, “How to define auxiliary class constructors.”
Problem
You want to define one or more auxiliary constructors for a Scala class to give consumers of the class different ways to create object instances.
Solution
Define the auxiliary constructors as methods in the class with the name this. You can define multiple auxiliary constructors, but they must have different signatures (parameter lists). Also, each constructor must call one of the previously defined constructors.
The following example demonstrates a primary constructor and three auxiliary constructors:
// primary constructor class Pizza (var crustSize: Int, var crustType: String) { // one-arg auxiliary constructor def this(crustSize: Int) { this(crustSize, Pizza.DEFAULT_CRUST_TYPE) } // one-arg auxiliary constructor def this(crustType: String) { this(Pizza.DEFAULT_CRUST_SIZE, crustType) } // zero-arg auxiliary constructor def this() { this(Pizza.DEFAULT_CRUST_SIZE, Pizza.DEFAULT_CRUST_TYPE) } override def toString = s"A $crustSize inch pizza with a $crustType crust" } object Pizza { val DEFAULT_CRUST_SIZE = 12 val DEFAULT_CRUST_TYPE = "THIN" }
Given these constructors, the same pizza can be created in the following ways:
val p1 = new Pizza(Pizza.DEFAULT_CRUST_SIZE, Pizza.DEFAULT_CRUST_TYPE) val p2 = new Pizza(Pizza.DEFAULT_CRUST_SIZE) val p3 = new Pizza(Pizza.DEFAULT_CRUST_TYPE) val p4 = new Pizza
Discussion
There are several important points to this recipe:
- Auxiliary constructors are defined by creating methods named
this
. - Each auxiliary constructor must begin with a call to a previously defined constructor.
- Each constructor must have a different signature.
- One constructor calls another constructor with the name
this
.
In the example shown, all of the auxiliary constructors call the primary constructor, but this isn’t necessary; an auxiliary constructor just needs to call one of the previously defined constructors. For instance, the auxiliary constructor that takes the crustType
parameter could have been written like this:
def this(crustType: String) { this(Pizza.DEFAULT_CRUST_SIZE) this.crustType = Pizza.DEFAULT_CRUST_TYPE }
Another important part of this example is that the crustSize
and crustType
parameters are declared in the primary constructor. This isn’t necessary, but doing this lets Scala generate the accessor and mutator methods for those parameters for you. You could start to write a similar class as follows, but this approach requires more code:
class Pizza () { var crustSize = 0 var crustType = "" def this(crustSize: Int) { this() this.crustSize = crustSize } def this(crustType: String) { this() this.crustType = crustType } // more constructors here ... override def toString = s"A $crustSize inch pizza with a $crustType crust" }
To summarize, if you want the accessors and mutators to be generated for you, put them in the primary constructor.
Although the approach shown in the Solution is perfectly valid, before creating multiple class constructors like this, take a few moments to read Recipe 4.5, “Providing Default Values for Constructor Parameters”. Using that recipe can often eliminate the need for multiple constructors.
Generating auxiliary constructors for case classes
A case class is a special type of class that generates a lot of boilerplate code for you. Because of the way they work, adding what appears to be an auxiliary constructor to a case class is different than adding an auxiliary constructor to a “regular” class. This is because they’re not really constructors: they’re apply
methods in the companion object of the class.
To demonstrate this, assume that you start with this case class in a file named Person.scala:
// initial case class case class Person (var name: String, var age: Int)
This lets you create a new Person
instance without using the new
keyword, like this:
val p = Person("John Smith", 30)
This appears to be a different form of a constructor, but in fact, it’s a little syntactic sugar — a factory method, to be precise. When you write this line of code:
val p = Person("John Smith", 30)
behind the scenes, the Scala compiler converts it into this:
val p = Person.apply("John Smith", 30)
This is a call to an apply
method in the companion object of the Person
class. You don’t see this, you just see the line that you wrote, but this is how the compiler translates your code. As a result, if you want to add new “constructors” to your case class, you write new apply
methods. (To be clear, the word “constructor” is used loosely here.)
For instance, if you decide that you want to add auxiliary constructors to let you create new Person
instances (a) without specifying any parameters, and (b) by only specifying their name, the solution is to add apply
methods to the companion object of the Person
case class in the Person.scala file:
// the case class case class Person (var name: String, var age: Int) // the companion object object Person { def apply() = new Person("<no name>", 0) def apply(name: String) = new Person(name, 0) }
The following test code demonstrates that this works as desired:
object CaseClassTest extends App { val a = Person() // corresponds to apply() val b = Person("Pam") // corresponds to apply(name: String) val c = Person("William Shatner", 82) println(a) println(b) println(c) // verify the setter methods work a.name = "Leonard Nimoy" a.age = 82 println(a) }
This code results in the following output:
Person(<no name>,0) Person(Pam,0) Person(William Shatner,82) Person(Leonard Nimoy,82)
See Also
- Recipe 6.8, “Creating Scala Object Instances Without Using the new Keyword” demonstrates how to implement the apply method in a companion object so you can create instances of a class without having to use the
new
keyword (or declare your class as a case class) - Recipe 4.5, “Providing Default Values for Scala Constructor Parameters” demonstrates an approach that can often eliminate the need for auxiliary constructors
- Recipe 4.14, “Generating Boilerplate Code with Scala Case Classes” details the nuts and bolts of how case classes work
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |