You’re working with match expressions, actors, or other situations
where you want to use the case class syntax to
generate boilerplate code, including accessor and mutator methods, along
with apply
, unapply
, toString
, equals
, and hashCode
methods, and more.
Define your class as a case class, defining any parameters it needs in its constructor:
// name and relation are 'val' by default
case
class
Person
(
name
:
String
,
relation
:
String
)
Defining a class as a case class results in a lot of boilerplate code being generated, with the following benefits:
An
apply
method is generated, so you don’t need to use thenew
keyword to create a new instance of the class.Accessor methods are generated for the constructor parameters because case class constructor parameters are
val
by default. Mutator methods are also generated for parameters declared asvar
.A good, default
toString
method is generated.An
unapply
method is generated, making it easy to use case classes in match expressions.equals
andhashCode
methods are generated.A
copy
method is generated.
When you define a class as a case class, you don’t have to use the
new
keyword to create a new
instance:
scala>case class Person(name: String, relation: String)
defined class Person // "new" not needed before Person scala>val emily = Person("Emily", "niece")
emily: Person = Person(Emily,niece)
Case class constructor parameters are val
by default, so accessor methods are
generated for the parameters, but mutator methods are not
generated:
scala>emily.name
res0: String = Emily scala>emily.name = "Fred"
<console>:10: error: reassignment to val emily.name = "Fred" ^
By defining a case class constructor parameter as a var
, both accessor and mutator methods are
generated:
scala>case class Company (var name: String)
defined class Company scala>val c = Company("Mat-Su Valley Programming")
c: Company = Company(Mat-Su Valley Programming) scala>c.name
res0: String = Mat-Su Valley Programming scala>c.name = "Valley Programming"
c.name: String = Valley Programming
Case classes also have a good default toString
method implementation:
scala> emily
res0: Person = Person(Emily,niece)
Because an unapply
method is
automatically created for a case class, it works well when you need to
extract information in match expressions, as shown here:
scala> emily match { case Person(n, r) => println(n, r) }
(Emily,niece)
Case classes also have generated equals
and hashCode
methods, so instances can be
compared:
scala>val hannah = Person("Hannah", "niece")
hannah: Person = Person(Hannah,niece) scala>emily == hannah
res1: Boolean = false
A case class even creates a copy
method that is helpful when you need to
clone an object, and change some of the fields during the
process:
scala>case class Employee(name: String, loc: String, role: String)
defined class Employee scala>val fred = Employee("Fred", "Anchorage", "Salesman")
fred: Employee = Employee(Fred,Anchorage,Salesman) scala>val joe = fred.copy(name="Joe", role="Mechanic")
joe: Employee = Employee(Joe,Anchorage,Mechanic)
Case classes are primarily intended to create “immutable records” that you can easily use in pattern-matching expressions. Indeed, pure FP developers look at case classes as being similar to immutable records found in ML, Haskell, and other languages.
Perhaps as a result of this, case class constructor parameters are
val
by default. As a reviewer of this
book with an FP background wrote, “Case classes allow var
fields, but then you are subverting their
very purpose.”
As shown in the Solution, when you create a case class, Scala
generates a wealth of code for your class. To see the code that’s
generated for you, first compile a simple case class, then disassemble
it with javap
. For example, put
this code in a file named Person.scala:
case
class
Person
(
var
name
:
String
,
var
age
:
Int
)
Then compile the file:
$ scalac Person.scala
This creates two class files, Person.class and Person$.class. Disassemble Person.class with this command:
$ javap Person
This results in the following output, which is the public signature of the class:
Compiled from "Person.scala" public class Person extends java.lang.Object implements scala.ScalaObject,scala.Product,scala.Serializable{ public static final scala.Function1 tupled(); public static final scala.Function1 curry(); public static final scala.Function1 curried(); public scala.collection.Iterator productIterator(); public scala.collection.Iterator productElements(); public java.lang.String name(); public void name_$eq(java.lang.String); public int age(); public void age_$eq(int); public Person copy(java.lang.String, int); public int copy$default$2(); public java.lang.String copy$default$1(); public int hashCode(); public java.lang.String toString(); public boolean equals(java.lang.Object); public java.lang.String productPrefix(); public int productArity(); public java.lang.Object productElement(int); public boolean canEqual(java.lang.Object); public Person(java.lang.String, int); }
Then disassemble Person$.class:
$ javap Person$
Compiled from "Person.scala"
public final class Person$ extends scala.runtime.AbstractFunction2 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, int);
public java.lang.Object readResolve();
public java.lang.Object apply(java.lang.Object, java.lang.Object);
}
As you can see, Scala generates a lot of source code when you declare a class as a case class.
As a point of comparison, if you remove the keyword case
from that code (making it a “regular”
class), compile it, and then disassemble it, Scala only generates the
following code:
public
class
Person
extends
java
.
lang
.
Object
{
public
java
.
lang
.
String
name
();
public
void
name_$eq
(
java
.
lang
.
String
);
public
int
age
();
public
void
age_$eq
(
int
);
public
Person
(
java
.
lang
.
String
,
int
);
}
That’s a big difference. The case class results in 22 more
methods than the “regular” class. If you need the functionality, this
is a good thing. However, if you don’t need all this additional
functionality, consider using a “regular” class
declaration instead. For instance, if
you just want to be able to create new instances of a class without
the new
keyword, like this:
val
p
=
Person
(
"Alex"
)
create an apply
method in the
companion object of a “regular” class, as described in Recipe 6.8. Remember, there isn’t anything in a case
class you can’t code for yourself.
Recipe 4.3, shows how to write additional
apply
methods so a case class can appear to have multiple constructors.A discussion of extractors on the official Scala website.
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.