Outdated Notice
Before we jump into case objects, we should provide a little background on “regular” Scala objects. As we mentioned early in this book, you use a Scala object
when you want to create a singleton object. As the documentation states, “Methods and values that aren’t associated with individual instances of a class belong in singleton objects, denoted by using the keyword object
instead of class
.”
A common example of this is when you create a “utilities” object, such as this one:
object PizzaUtils {
def addTopping(p: Pizza, t: Topping): Pizza = ...
def removeTopping(p: Pizza, t: Topping): Pizza = ...
def removeAllToppings(p: Pizza): Pizza = ...
}
Or this one:
object FileUtils {
def readTextFileAsString(filename: String): Try[String] = ...
def copyFile(srcFile: File, destFile: File): Try[Boolean] = ...
def readFileToByteArray(file: File): Try[Array[Byte]] = ...
def readFileToString(file: File): Try[String] = ...
def readFileToString(file: File, encoding: String): Try[String] = ...
def readLines(file: File, encoding: String): Try[List[String]] = ...
}
This is a common way of using the Scala object
construct.
Case objects
A case object
is like an object
, but just like a case class has more features than a regular class, a case object has more features than a regular object. Its features include:
- It’s serializable
- It has a default
hashCode
implementation - It has an improved
toString
implementation
Because of these features, case objects are primarily used in two places (instead of regular objects):
- When creating enumerations
- When creating containers for “messages” that you want to pass between other objects (such as with the Akka actors library)
Creating enumerations with case objects
As we showed earlier in this book, you create enumerations in Scala like this:
sealed trait Topping
case object Cheese extends Topping
case object Pepperoni extends Topping
case object Sausage extends Topping
case object Mushrooms extends Topping
case object Onions extends Topping
sealed trait CrustSize
case object SmallCrustSize extends CrustSize
case object MediumCrustSize extends CrustSize
case object LargeCrustSize extends CrustSize
sealed trait CrustType
case object RegularCrustType extends CrustType
case object ThinCrustType extends CrustType
case object ThickCrustType extends CrustType
Then later in your code you use those enumerations like this:
case class Pizza (
crustSize: CrustSize,
crustType: CrustType,
toppings: Seq[Topping]
)
Using case objects as messages
Another place where case objects come in handy is when you want to model the concept of a “message.” For example, imagine that you’re writing an application like Amazon’s Alexa, and you want to be able to pass around “speak” messages like, “speak the enclosed text,” “stop speaking,”, “pause,” and “resume.” In Scala you create singleton objects for those messages like this:
case class StartSpeakingMessage(textToSpeak: String)
case object StopSpeakingMessage
case object PauseSpeakingMessage
case object ResumeSpeakingMessage
Notice that StartSpeakingMessage
is defined as a case class rather than a case object. This is because a case object can’t have any constructor parameters.
Given those messages, if Alexa was written using the Akka library, you’d find code like this in a “speak” class:
class Speak extends Actor {
def receive = {
case StartSpeakingMessage(textToSpeak) =>
// code to speak the text
case StopSpeakingMessage =>
// code to stop speaking
case PauseSpeakingMessage =>
// code to pause speaking
case ResumeSpeakingMessage =>
// code to resume speaking
}
}
This is a good, safe way to pass messages around in Scala applications.