The beauty of Scala case classes

One of my favorite features in Scala are case classes. Below is an example of the declaration of a case class:

case class Student(name:String, age:Int)

The declaration looks pretty simple, isn’t it?

Now let’s do it the java way:

public class Student {
  private String name;
  private int age;

  public Student(String name, int age) {
     this.name = name;
     this.age = age;
  }

  public String getName() {
    return this.name;
  }

  public String setName(String name) {
     this.name = name;
  }

  public int getAge() {
    return this.age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public boolean equals(Object obj) {
    ...
  }

  public int hashCode() {
    ...
  }

  public String toString() {
    ...
  }

Which method do you prefer.
Scala case classes are extremely powerful and their main use is not limited to writing abbreviated java beans.

By declaring a case class we get for free the equals, toString and hashCode methods. Nice, can it get any better? Yes….
You can easily create instances of a case class without having to declare a constructor and no need to use the new keyword:

val students = List(Student("John", 20), Student("David", 21))

Case classes are used for pattern matching. Pattern matching is similar to Java switch statements however it is many times more powerful. The example below shows a typical use for case classes with pattern matching:

abstract class Expression
case class Sum(a1:Int, a2:Int) extends Expression
case class Diff(a1:Int, a2:Int) extends Expression
case class Mult(a1:Int, a2:Int) extends Expression

def doOperation(expr:Expression):Int = expr matches {
  case Sum(a1, a2) => a1 + a2
  case Diff(a1, a2) => a1 - a2
  case Mult(a1, a2) => a1 * a2
}

Adding methods to any class using Scala implicit class

Scala has many useful features – but one of the most useful is the ability to add any method to any class.

I will start with an example from the ATG world as this is a concrete example for me where this facility is extremely useful.
Let’s say you want to add a method called valideBespoke() to GenericFormHandler so that it can be used by any of your bespoke form handlers. Assuming that all the form handlers extend GenericFormHandler, all you need to do is, to create another class BetterGenericFormHandler class which extends GenericFormHandler. Then change all the bespoke formhandlers to extend this class. At this point you can use the method validateBespoke().

However this approach will not work if you extend existing ATG form handlers which in turn extend the origin GenericFormHandler class.

In Scala the solution to this problem is to use implicit classes. An implicit class wraps an existing class and allows an addition of any number of methods. It only works for new methods – not for overriding existing methods.
But still pretty useful. See an example below:

  class GenericFormHandler {
    def originalDoSomething(): String = {
      "this is the original method"
    }
  }
  implicit class BetterGenericFormHandler(frh: GenericFormHandler) {
    def validateMethod(): Boolean = {
      true
    }

    def originalDoSomething(): String = {
      "this is the better method"
    }
  }

  class CustomGenericFormHandler extends GenericFormHandler {
    def doSomething(): Boolean = {
      this.validateMethod
      // do more stuff
    }
  }

There are only a few restrictions:

  • An implicit class needs to be defined inside another class
  • The implicit class only can take one non-implicit argument
  • The implicit class name should not clash with the name of a method, member or object in scope.
  • You need to import the implicit class definition where you need to use it It would be nice if it worked without imports, but it doesn’t. Still a small price to pay.
  • It does not allow to override existing methods in the class.

Please see the following link for more details about implicit classes in Scala:

Note that to solve the problem above you don’t need to use implicit classes. In Scala, a Trait allows to define a method with a body. As long as your custom form handler uses that Trait, in the same way you can access the validateMethod().