How to Scala, Written by a Java: 2 – Classes and Objects

(Find these examples at my GitHub))

Classes in scala are defined in a similar manner to how they are defined in Java.

class ClassBasic{
  
}

This is the simplest possible class. It has no constructor. It has no methods. It does nothing. Its uninteresting.

1. Constructors

Let’s give it a constructor.

class ClassBasic(_number: Int){
  private val number = _number
}

In Scala, the constructor is defined by the class body itself. the arguments to the constructor are placed after the class name. The body of the constructor is accounted by the statements and expressions in the body of the class.

To make the constructor private or protected you place the appropriate word before the arguments. The default access level in Scala is public.

class ClassBasic private(_number: Int){
  private val number = _number
}

Since the body of the class is the constructor body, there is a very limited sense of constructor overloading in Scala. You cannot have two different constructors that do wildly different things. However you can create overloaded constructors by calling another constructor.

class ClassBasic(_number: Int){
  def this(numberString: String) = this(numberString.toInt)
  def this() = this(0)
  
  private val number = _number
}

Here we have three constructors. The first constructor takes an Int and sets a member to the argument’s value. The second takes a String and calls the first constructor on the Int representation of the passed in String. The third takes no arguments and is a simple currying of the first constructor. All three of them at the end of the day call the same code.

Let’s make our class a little more interesting for the rest of the post.

class ClassBasic(_number: Int, _text: String){
  def this(_text: String) = this(0, _text)
  def this(_number: Int) = this(_number, "")
  
  private val number = _number
  private val text = _text
  
  private val concat = text + number
}

2. Methods

Methods are simply functions within a class. You define them like any other function.

  def getNumber(): Int = {
    number
  }
  def getText(): String = {
    text
  }
  def getValue(): String = {
    concat
  }

These define the get methods for the members of the class. More succinctly, we may write:

  def getNumber(): Int = number
  def getText(): String = text
  def getValue(): String = concat

Finally we’ll give our class all the bells and whistles by overriding the toString, equals and hashCode methods. In Scala, every method which override a method defined in a super class must be labelled with override.

  override def toString(): String = {
    "number = " + number + "\n" +
    "text = " + text + "\n" +
    "concat = " + concat
  }
  override def equals(any: Any): Boolean = {
    if(!any.isInstanceOf[ClassBasic]){
      false
    }
    else{
      val other = any.asInstanceOf[ClassBasic]
      (getNumber() equals other.getNumber()) &&
      (getText() equals other.getText())
    }
  }
  override def hashCode(): Int = concat.hashCode()

3. Non-instance Members

Non-instance members are typically referred to as static. These are members of the class which exist outside any instance of the class. In Scala, there is a clear distinction between instance and non-instance class members. The keyword object implies the members within exist outside the class instances. In Java terms the object is where all the static members of the class would be written. Taking advantage of this is easy. Our class becomes:

object ClassBasic{
  private val DEFAULT_NUMBER = 0
  private val DEFAULT_TEXT = ""
}
class ClassBasic(_number: Int, _text: String){
  def this(_text: String) = this(ClassBasic.DEFAULT_NUMBER, _text)
  def this(_number: Int) = this(_number, ClassBasic.DEFAULT_TEXT)
  
  private val number = _number
  private val text = _text
  
  private val concat = text + number
  
  def getNumber(): Int = number
  def getText(): String = text
  def getValue(): String = concat
  
  override def toString(): String = {
    "number = " + number + "\n" +
    "text = " + text + "\n" +
    "concat = " + concat
  }
  override def equals(any: Any): Boolean = {
    if(!any.isInstanceOf[ClassBasic]){
      false
    }
    else{
      val other = any.asInstanceOf[ClassBasic]
      (getNumber() equals other.getNumber()) &&
      (getText() equals other.getText())
    }
  }
  override def hashCode(): Int = concat.hashCode()
}

We say this is the class ClassBasic and its companion object. The companion object is defined in the same file as the class, takes the same name as the class and has access to all the members of the class. Likewise, the class has access to all the members of its companion object. Common uses are holding non-instance members and creating factory methods for your class (note the singleton is a special case of the factory).

  //factory
  def apply(number: Int, text: String): ClassBasic =
    new ClassBasic(number, text)
  def apply(number: Int): ClassBasic =
    new ClassBasic(number)
  def apply(text: String): ClassBasic =
    new ClassBasic(text)

The apply method is a special method in Scala. The language provides some syntactic sugar to make calling this method easy. The following two code snippets are provide the same results.

val number = 9
val text = "nine"
val basic1 = ClassBasic.apply(number, text)
val basic2 = ClassBasic.apply(number)
val basic3 = ClassBasic.apply(text)
println(basic1 + "\n" + basic2 + "\n" + basic3)
val number = 9
val text = "nine"
val basic1 = ClassBasic(number, text)
val basic2 = ClassBasic(number)
val basic3 = ClassBasic(text)
println(basic1 + "\n" + basic2 + "\n" + basic3)

Next we’ll take a look at for comprehensions.