A Scala tutorial for Java developers

Dima Statz
Apr 20 · 6 min read

Introduction

I was asked to prepare a 30-min presentation about the Scala programming language. The target audience is a community of experienced Java developers in our organization. Taking into account the time limit and the audience type, I will go straight to the business an I will cover the core Scala features.


Scala pros: what makes it great

Concise syntax

Scala is designed to be concise, many of Scala’s design decisions aimed to address the verbosity of Java. For example here is the code that defines new class UserInfo. This class has two properties. The first one is read-write property Name and the second one is BirthDate which is read-only.

class UserInfo {
private String name;
private LocalDate birthDate;

public UserInfo(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}

public String getName() {
return name;
}

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

public LocalDate getBirthDate() {
return birthDate;
}
}
class UserInfo(var name: String, val birthDate: LocalDate)

Case Classes

When it comes to comparing objects in Java, it compares references. For example, the following code will return false.

LocalDate date = LocalDate.now();
UserInfo a = new UserInfo("John", date);
UserInfo b = new UserInfo("John", date);
return (a == b);
case class UserInfo(var name: String, birthDate: LocalDate)val date = LocalDate.now()
val a = new UserInfo("John", date)
val b = new UserInfo("John", date)
a == b // returns True

Pattern matching

Pattern matching is a mechanism for checking a value against a pattern. You can think about Scala’s pattern matching as a more powerful version of switch statement in Java. A match expression has a value, the match keyword, and at least one caseclause. Example:

def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
def matchOnType(x: Any): String = x match {
case x: Int => s"$x is integer"
case x: String => s"$x is string"
case _ => "unknown type"
}
matchOnType(1) // returns 1 is integer
matchOnType("1") // returns 1 is string
def matchList(x: List[Int]): String = x match {
case List(_) => "a single element list"
case List(_, _) => "a two elements list"
case List(1, _*) => "a list starting with 1"
}
matchList(List(3)) // returns a single elements list matchList(List(0, 1)) // returns a two elements list
matchList(List(1, 0, 0)) // returns a list starting with 1
case class UserInfo(var name: String, birthDate: LocalDate)def isUserJohn(x: Any): Boolean = x match {
case UserInfo("John", _) => true
case
_ => false
}
val list = List(
"wrong user type",
UserInfo("Ben", LocalDate.now()),
UserInfo("John", LocalDate.now()))
list.filter(isUserJohn) // list with one element UserInfo John

Implicit Classes

The implicit keyword makes the class’s primary constructor available for implicit conversions when the class is in scope.

object Extensions {
implicit class UserInfoExt(user: UserInfo) {
def getAge(): Int = {
Years.yearsBetween(LocalDate.now(), user.birthDate,).getYears
}
}
}
import Extensions._
val user = new UserInfo("John", LocalDate.now())
user.getAge()

Higher-order functions

Scala allows the definition of higher-order functions. These are functions that take other functions as parameters, or whose result is a function. The classic examples of higher-order functions are map and filter.

def multiply(x: Int): Int = x * 2

List(1, 2, 3).map(multiply) // returns 2 4 6
def isEven(x: Int): Boolean = x % 2 == 0
List(1, 2, 3).filter(isEven) // returns 2
def apply(f: Int => String, x: Int): String = f(x)
def printInt(x: Int): String = s"Printing integer $x"
apply(printInt, 3)

Option Monad

Monad is a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic and provides an easy way for composing and sequencing operations on some contained value(s).

class Document {
def getActivePage: Page = ???
}
class Page {
def getSelectedText: String = ???
}
def getSelectedTextLength(doc: Document): Int = {
if(doc != null) {
val page = doc.getActivePage
if(page != null){
val text = page.getSelectedText
if(text != null){
text.length
}
else 0
}
else 0
}
else 0
}
def getSelectedTextLength(doc: Document): Int = {
if(doc == null)
return 0

val page = doc.getActivePage
if(page == null)
return 0

val text = page.getSelectedText
if(text == null)
return 0

text.length
}
def getSelectedTextLength(doc: Document): Int = {
try {
doc.getActivePage.getSelectedText.length
}
catch {
case _: NullPointerException => 0
case e: Exception => throw e
}
}
trait Option[A] {
def flatMap[B](f: A => Option[B]): Option[B]
}

case class None[A]() extends Option[A] {
def flatMap[B](f: A => Option[B]): Option[B] = new None
}

case class Some[A](a: A) extends Option[A] {
def flatMap[B](f: A => Option[B]): Option[B] = {
f(a)
}
}
class Document {
def getActivePage: Option[Page] = ???
}
class Page {
def getSelectedText: Option[String] = ???
}

def getSelectedTextLength(doc: Option[Document]): Int = {
doc
.flatMap(_.getActivePage)
.flatMap(_.getSelectedText)
.map(_.length).getOrElse(0)
}

Summary

Scala isn’t perfect, it comes with its own limitation and drawbacks. It has a steep learning curve because its principles are based on mathematical type theory which is hard to master. When not maintained carefully, the Scala source code is hard to read and understand. Some big tech companies like LinkedIn, Twitter, Yammer reported that they are abandoning or decreasing their dependency on Scala.