Hello, dear Reader! I apologize for a such loud title of the article, but you should know how it’s important for me to engage the readers to discussion. Not so long time ago, I decided to systemize my knowledge of object oriented programming in Scala. That was awesome idea! And now I want to share with you all mini-topics which I underlined as important, but many developers forget about them.

Of course you will find that something is very trivial, but I bet you definitely will be surprised at least with one out of nine points. At least I hope so 🙂 Feel free to add your favorite Scala OOP tricks in the comments, if I didn’t mention them.

#1 Class constructors

Do you know how to create more than one constructor in a class or in a case class? Scala allows to do this. Buy the way you need to use this keyword.

case class Account(id: String,
                   balance: Double,
                   status: Boolean) {

  def this(id: String) = this(id, 0, false)
  def this(id: String, balance: Double) = this(id, balance, false)
  def this(id: String, status: Boolean) = this(id, 0, status)

}

val a1 = new Account("a") // Account(a,0.0,false)
val a2 = new Account("b", 100) // Account(b,100.0,false)
val a3 = new Account("b", true) // Account(b,0.0,true)
val a4 = new Account("b", 450, false) // Account(b,450.0,false)

Such constructor declaration can be applied either to common classes or to case classes. All constructors created with help of this keyword are called secondary constructors.
Keep in mind that the same result can be achieved with help of default values declaration.

#2 Traits mixing during object initialization

Mixins in Scala are a common practice. You simply create multiple traits and then add them to a class declaration using with keyword. But what if you want to apply traits dynamically depends on situation? It’s also possible!

class Worker(health: Int) {
  def mineMinerals() = s"Crafting minerals"
}

trait GasCrafter {
  def mineGas() = "Crafting gas..."
}

val universalWorker = new Worker(45) with GasCrafter

universalWorker.mineMinerals()
universalWorker.mineGas()

You can mix as many traits as you want. But as you may guess it’s very easy to write a code with long mixins and in a result lost its readability.

#3 Java developers & Scala OOP

Don’t start a holy war with addicted Java developers about advantages of Scala OOP. A concise Scala code doesn’t disturb them at all. For each your argument they have a separate library: auto-generation of getters and setters, implementation of immutability etc.
I hope this advice will save a lot of your time 😀

#4 Mutable case class arguments

Scala developers know that data implementation starts from case classes. Since Scala propagates immutability it’s ok that by default all arguments of a case class can not be overwritten. So when you want to change case class arguments after object creation, use var keyword:

case class Person(name: String)

val person = Person("Alex")
person.name = "Bob" //Reassignment to val

case class Friend(var name: String)

val friend = Friend("Sam")
friend.name= "Bob" //Ok

friend.name // Bob

So here is everything absolutely up to you. If you want to close the eyes on Scala recommendations and best practices regarding the variables immutability it’s ok, simply use var keyword in a case class declaration. But if you want to code in a functional style, the paragraph #6 is for you!

#5 Access modifiers

Scala has very agile system of access modifiers. Despite that their number less than in Java (public is absent explicitly), they are more powerful and give more grained control over class and object fields. In a context of nested classes, inheritance and packages you can declare fields with such access modifiers:

package myPac

class Foo {

  private[this] val a = 10
  protected val b = 50
  private[free] val c = 15 * commonConstant 

  class FooInner {
    private[Foo] val d = 30

    private[myPac] def doJob() = {}
  }

}

In the square brackets we set a precise access level. So instead of default behavior of private and protected keywords we can increase or decrease their effect.

And here is a code for myPac package:

package object myPac {
  val commonConstant = 100
}

#6 “Change” state of immutable objects

When you are trying to adhere functional programming principles you have to work with immutable values. But this does not mean that you don’t need to change these values. For example case classes have an auto-generated method copy. It serves for creation of a new object based on old one. Here is a code sample:

case class Account(id: String, balance: Double, status: Boolean)

def withdraw(amount: Double, acc: Account): Account = {
  acc copy(balance = acc.balance - amount)
}

def ban(acc: Account): Account = acc copy(status = false)

def toDefault(acc: Account): Account = {
  acc copy(balance = 100, status = true)
}


val acc1 = Account("v0A0q1", 100, true)

val accAfterWithdraw = withdraw(25, acc1) //Account(v0A0q1,75.0,true)
val bannedAcc = ban(accAfterWithdraw) //Account(v0A0q1,75.0,false)
val restoredToDefaultAcc = toDefault(bannedAcc) //Account(v0A0q1,100.0,true)

With help of copy method you can create new object by changing from 0 to N arguments.

#7 Methods apply & unapply

Frequently Scala developers call Scala a “data centric” programming language. In many senses this is due to case classes and operations which you can do with them. For each case class Scala generates two methods apply & unapply.

Using these methods you can extract arguments from a case class in a tuple form and vice versa.

case class Account(id: String, balance: Double, status: Boolean)

val acc = Account("a01", 10, true)
Account.unapply(acc) // Some((a01,10.0,true))

val dataSet = ("a01",10.0,true)
(Account.apply _).tupled(dataSet) // Account(a01,10.0,true)

You must remember that unapply is an extractor method. Thanks to it a pattern matching can be applied to the object with appropriate unapply method.

#8 Keyword sealed

During a class (trait) creation you can decorate it with sealed keyword. This circumstance applies one very important restriction to the class (trait) – it can not be extended outside of the source file where it is created.

sealed abstract class LegacyDriver {
  def connect(): Unit
}

class ReactiveDriver extends LegacyDriver {
  override def connect(): Unit = {
    println("Reactive connection...")
  }
}

class SQLDriver extends LegacyDriver {
  override def connect(): Unit = {
    println("SQL connection...")
  }
}

In the code snippet above LegacyDriver is marked with sealed keyword. It’s ok to extend it directly inside of its source, but its extension doesn’t allowed outside of it. When sealed keyword is useful? Well, when you create some particular data hierarchy, you may want to keep some implementations in one place. This is a good use case for sealed.

#9 Object & companion

Scala has its own builtin mechanism for a singleton – object keyword. You can declare an object and then use its members: values and methods. But what is the difference between an object and a companion object in Scala? A companion object is the object with the same name as a class which is created in the same source file.

case class Book(pages: Int, ratio: Int)

object Book {
  def isGoodForJourney(book: Book) = {
    if (book.pages < 200 & book.ratio > 3) true
    else false
  }
}

val b1 = Book(190, 4)
Book.isGoodForJourney(b1)

Summary

That’s it! I believe you have found something new for yourself or at least refreshed in memory a Scala OOP theory. If you liked the post, please share it on Twitter or Facebook. Next week I’m going to publish new article with analysis of Scala & Java development. So let’s stay in touch – subscribe for a newsletters!

About The Author

Mathematician, programmer, wrestler, last action hero... Java / Scala architect, trainer, entrepreneur, author of this blog

  • Rafal, your comments are awesome 🙂
    I like everything you mentioned except Monocle 😀 It’s a third party lib, while the article is about pure Scala 🙂

    • Rafał Zelek

      Oh, ok I see your point. Anyway, I already posted the link for Monocycle library, so someone can explore it deeper.

  • Сергей Шоботов

    Sorry, but nothing new, everything you can get from Martin’s book:) It would be awesome to mention about unapplySeq, that makes possible do extract List, as well that putting implicit conversion for the class into companion object allows compiler bring it on demand automatically that makes companion objects more valuable.

  • Enpassant

    #1 You can use default values.

    case class Account(id: String, balance: Double = 0, status: Boolean = false)

Close