You want to define one or more auxiliary constructors for a class to give consumers of the class different ways to create object instances.
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
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.
Note
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. Using that recipe can often eliminate the need for multiple constructors.
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)
Recipe 6.8, demonstrates how to implement the
apply
method in a companion object so you can create instances of a class without having to use thenew
keyword (or declare your class as a case class).Recipe 4.5, demonstrates an approach that can often eliminate the need for auxiliary constructors.
Recipe 4.14, details the nuts and bolts of how case classes work.
Get Scala Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.