Scala Tutorials Part #6 - Case Classes


Case Classes

Case classes are very similar to classes, but they do lot of boiler plate stuff and have some neat functionality.

This is part 6 of the scala tutorial series. Check here for the full series.

: This article has been translated to chinese by ChanZong Huang, you can check it out here

Index

Declaring case classes

The code below declares a class called Book with three member variables id,title,isbn

case class Book(id:Int,title:String,isbn:Long)

Perfectly valid syntax and it takes just one line of code.

We would be tempted to put it along wherever we wan’t since it is just one line, but the scala style guide pretty much sumps up on the styling part.

Of course this can be different based on the requirement, one can group together multiple case classes into one single file with having similar functionality. This discussion is a separate topic, but if you are in doubt on where to place a case class, then put it in a separate file.

Creating classes without the new keyword

Case classes can be created without using the new keyword.

  val x = Book(100,"Stephen hawking's : A brief history of time",9788370017361L)

This is just for removing verbosity, in fact we can include the new keyword and it works in the same manner.

Can be compared syntactically to java strings where they can can be created without using the new keyword as well.

Accessing variables

Variables can be accessed similar to their class counterparts.

  val book = Book(100,"Stephen hawking's : A brief history of time",9788370017361L)

  println(book.id)
  println(book.title)
  println(book.isbn)

Note that in case classes, the variables are just val by default and not private val as in regular classes and hence there are no need of special getters.

Default and partial constructor values

We saw in the previous tutorial on how default values can be given to classes using the primary constructor. Same can be done in case classes as well.

case class Book(
private val id:Int = -1,private val title:String = "No name",private val isbn:Long = -1
)

We can then create an instance and access the variables as follows.

   val book = Book()

   println(book.id)
   println(book.title)
   println(book.isbn)

The programmer has the freedom to choose which values to supply and which values to leave it as default. In the below example only the id parameter is supplied, rest are the default values.

   val book = Book(100)

   println(book.id)
   println(book.title)
   println(book.isbn)

If we wan’t to provide a custom book value, then we can do that as below.

   val book = Book(title = "Lord of the Rings")

   println(book.id)
   println(book.title)
   println(book.isbn)

The second style of providing the variable identifier and then providing the value is considered good practice since it mentions what variable is being provided a custom value in an explicit manner.

One thing to note that this style of giving partial constructor values is also possible in regular scala classes, but it is kind of better suited to case classes.

Immutable objects

Case classes by default are immutable i.e once declared they cannot be changed. In the previous tutorial we briefly touched upon something similar in classes. But case classes are naturally suited in creating immutable classes and they have a whole suite of advantages which we will see below.

Constructor no value

In the case of immutable classes then you need not worry about direct variable access/creating getters and setters since they cannot be changed. This gives you a much more neater syntax.

All of the other stuff for regular classes such as val/var field promotion,getters/setters still apply here as well.

Structural equality - Automatic equals generation

When comparing objects in java, one of the most painful tasks in java is structural equality i.e comparing the individual variables/members of the class.

Case classes generate a lot of boilerplate which includes equals comparison. We can simply compare two classes as below.

  val book1 = Book(100,"Fifty Shades of Grey",9788490322178l)

  val book2 = Book(100,"Fifty Shades of Grey",9788490322178l)

  //Prints true
  println(book1 == book2)

As a seasoned java developer you would have been taught not to use == on strings/classes and use equals method. It is not quite the same in scala, but you can of course use println(book1.equals(book2)) and it would still print true.

The == also prints true and it is something called a synthetic method also called a bridge method which actually calls the equals method. It is generated by the scala compiler as a shorthand/convenience.

In our journey through scala we would frequently encounter such methods. It is not necessary to understand the implementation that is beneath it.

Reference equality

We might encounter situations where we want to compare the actual references.

In java you would use == as above, but scala has made a design decision of using double equals for structural equality since it is more used in real world scenarios. There are specialized methods to compare references, two in particular i.e eq and ne

  val book_1 = Book(title = "Something else")

  val book_2 = Book(title = "Something else")

  //book_1 and book_2 are different references
  //Evaluates to false
  println(book_1 eq book_2)

  //using a reference copy
  val book_1_copy = book_1

  //Evaluates to true
  println(book_1 eq book_1_copy)

ne is the exact opposite of eq.

  val book_1 = Book(title = "Something else")

  val book_2 = Book(title = "Something else")

  //Evaluates to false
  println(book_1 eq book_2)

  //Evaluates to true
  println(book_1 ne book_2)

These are synthetic methods similar to == and members of the AnyRef class

String representation

In a normal java/scala class if you call toString on the object it would typically return the hex string representation of the hashcode which in most of the cases completely useless. Case classes have a pretty nice toString method which gives a meaningful string representation of the class.

  val book = Book(100,"Fifty Shades of Grey",9788490322178l)

  //Prints Book(100,Fifty Shades of Grey,9788490322178)
  println(book.toString)
  

This is much better than the default toString present in regular classes and helpful in debugging. If you application requires something new, then you can always override and provide a customized implementation.

Automatic hashcode generation

The main takeaway from this discussion of hashcode in scala is that there is a contract with equals and hashcode and when a class changes these guarantees must be taken care. In case classes, this is done automatically.

Case class copy

An instance of a case class can easily be copied around to other case class instances.

This is kind of different from a clone. Copy creates a new object behind the scenes, but it is kind of abstracted away from the programmer.

Once again we will take the Book case class.

case class Book(id:Int,title:String,isbn:Long)
  case class Book(id:Int,title:String,isbn:Long)

  val book1 = Book(100,"The Lord of the Rings : The fellowship of the ring",9780261103573l)

  val book_fullcopy = book1.copy()

  //Will copy all of the members
  println(book_fullcopy.id)
  println(book_fullcopy.title)
  println(book_fullcopy.isbn)

  
  case class Book(id:Int = 2000,title:String,isbn:Long)

  val book1 = Book(100,"The Lord of the Rings : The fellowship of the ring",9780261103573l)

  val book_partialcopy = book1.copy(title = "The Lord of the Rings : The two towers")

  //Only the title is changed. Rest remains the same
  println(book_partialcopy.id)
  println(book_partialcopy.title)
  println(book_partialcopy.isbn)

  

Notice that we cannot just copy one value and leave the rest non-existent i.e creating an instance only with the id variable, because that would change the structure of the object itself.

Case class decompiled

A good way to understand how toString, equals and hashCode work is by looking at the decompiled class

We can see that there are methods generated for equals, toString and hashCode. A default constructor is also generated.

Some other methods such as copy and getters for the fields of the case class are present which is common to regular classes as well.

There are two main topics that are very closely related to case classes i.e the apply method and pattern matching. In fact the very name for case classes come from matching cases for several patterns, but I am not going to explain them here since they are complex topics of their own and I will be covering them in later tutorials.

One other topic that I left off for further discussion is case class inheritance. Inheritance in case classes is somewhat tricky because of automatic equals and hashcode. I will be explaining them once I cover traits since they give a better way to performance inheritance.

Stay tuned


Tagged Under


Scala


Search this website...




Keeping up with blogs...

I blog occasionally and mostly write about Software engineering and the likes. If you are interested in keeping up with new blog posts, you should follow me on twitter where I usually tweet when I publish them. You can also use the RSS feed , or even subscribe via email below.

Feedio Subscribe


Share & Like

If you like this post, then you can either share/discuss/vote up on the below sites.



Thoughts ...

Please feel free to share your comments below for discussion

Blog comments powered by Disqus