Scala · Tutorial

How to use Case Classes in Scala

Scala has adopted many concepts from other functional programming languages: higher-order functions from Haskell, actors model from Erlang, futures from Clojure, etc. However, Scala has also introduced new tools in the functional programming world: case classes is one of them. Case classes are a special type of classes, as the compiler automatically adds some useful methods to them, and this makes them particularly interesting for pattern matching and data definition. This article describes how to use them.

Defining a Case Class

A case class is identified by the case keyword. An example of case class is following:

scala> case class Person(name: String, age: Int)
defined class Person

Note that no body is needed — you can omit curly braces when empty!

Every time the Scala compiler finds a case class, it automatically adds some convenience methods.

The first one that the compiler generates is a factory method: it allows to create instances of the case class without the need of using the new keyword. Following are three equivalent ways of creating a Person instance:

scala> new Person("Mr Dude", 18)
res0: Person = Person(Mr Dude,18)

scala> Person.apply("Mr Dude", 18)
res1: Person = Person(Mr Dude,18)

scala> Person("Mr Dude", 18) 	
res2: Person = Person(Mr Dude,18)
// The most concise and elegant one!

Having a factory method may not seem a big advantage, but it will make your code easier to read, in particular when dealing with creation of nested objects.

Moreover, all the parameters are converted in immutable values that can be easily accessed without worrying about any side effect:

scala> val dude = Person("Mr Dude", 18)
dude: Person = Person(Mr Dude,18)

scala> dude.name
res3: String = Mr Dude

scala> dude.age
res4: Int = 18

scala> dude.age = 19 
<console>:10: error: reassignment to val
       dude.age = 19
                ^
// Immutability is guaranteed!

Last but not least, the Scala compiler adds intuitive implementations of toString, hashCode, equals that exploit the structure of the class and the value of its parameters. For example, two classes are considered equal if they have the same structure and its parameters are recursively equal:

scala> val dude = Person("Mr Dude", 18)
dude: Person = Person(Mr Dude,18)

scala> val sameDude = Person("Mr Dude", 18)
sameDude: Person = Person(Mr Dude,18)

scala> val anotherDude = Person("Another Dude", 18)
anotherDude: Person = Person(Another Dude,18)

scala> dude equals sameDude
res3: Boolean = true

scala> dude equals anotherDude
res4: Boolean = false

scala> dude equals List()
res5: Boolean = false

Using a Case Class

Case classes are perfect data containers: not only they make the data easy to access and compare, but they also guarantee immutability — a really important aspect to keep in mind when dealing with concurrency!

Also, thanks to their implicitly generated factory method, they naturally support pattern matching:

scala> def sayHello(entity: Any) = entity match {
	| case Person("Mr Dude", _) => "Yo Dude!"
	| case Person(name, _) => s"Hello $name"
	| case _ => "You are not even a person! Go away!"
	| }
sayHello: (entity: Any)String


scala> sayHello(Person("Mr Dude", 18))
res6: String = Yo Dude!

scala> sayHello(Person("James Bond", 50))
res7: String = Hello James Bond

scala> sayHello(List())
res8: String = You are not even a person! Go away!

Summary

This article has discussed the use of case classes in Scala. Case classes are a novel functional programming feature in Scala, particularly useful when used as data containers and for pattern matching.

One thought on “How to use Case Classes in Scala

  1. Nice explanation! One thing I noticed is that sometimes people use case classes exclusively to have a nice factory method, even when their classes are not data containers. I think that should be discouraged as we don’t want toString/equals/hashcode to be generated when we don’t need any of those at all!

    Like

Leave a comment