10 Scala Features Most* Java Developers Love
(*) “Most” in this case meaning: Of those I have met and have talked to about this. I’m not actually aware of an empirical study supporting this. But I still think it’s a fair claim. 😉
Some Java developers seem to be intimidated by Scala. They get hung up on some possibly rather obscure feature, and from that conclude that the language must be a complicated maze you can only get lost in. There are so many features in Scala, though, that are so easily accessible, and that really just make the developer’s life easier. This is my list of 10 features that are easy to understand and use, yet very powerful. I presented it to some groups of Java developers throughout 2014, and have been wanting to convert it into a blog post ever since. Now, with some delay, here it is, I hope you enjoy reading it.
1. Functions
This is about Scala features, not about the functional programming paradigm in general, so I’m not going to dive into which benefits the functional approach has in this post. Things you might want to look out for are how referential transparency makes it easier to reason about the correctness of programs, and how immutable data structures help you to stay sane when developing concurrent software.
A function in Scala is a “first-class value”. Like any other value, it can be passed as a parameter or returned as a result.
- A value can be of a function type
- Functions can be passed as arguments
- Functions can be returned as results
(Functions which take other functions as parameters or return them as results are called higher-order functions.)
Generally, a function is a mapping from values of one domain to another. You can write them as function literals:
val f = (x:String) => x.length
– the type of f
is String => Int.
val sum = (x:Int, y:Int) => x + y
– the type of sum
is (Int, Int) => Int
.
Or you can use methods as functions.
Let’s look at an example for the usefulness of functions. I found this one in a PDF of a draft edition of “Programming in Scala” on the internet. I think it never made it into the printed edition, so hopefully the authors don’t mind me stealing it. So let’s say we have the following definitions (written in Scala method syntax for readability, so don’t be irritated they look slightly different from the function literals above):
A function to sum integers between a and b
def sumInts(a: Int, b: Int): Int = if (a > b) 0 else a + sumInts(a + 1, b)
A function to sum the squares of integers between a and b:
def square(x: Int): Int = x * x
def sumSquares(a: Int, b: Int): Int =
if (a > b) 0 else square(a) + sumSquares(a + 1, b)
And a function to sum the powers of 2 of integers between a and b:
def powerOfTwo(x: Int): Int = if (x == 0) 1 else 2 * powerOfTwo(x - 1)
def sumPowersOfTwo(a: Int, b: Int): Int = if (a > b) 0 else powerOfTwo(a) + sumPowersOfTwo(a + 1, b)
These functions are variants of sum of f(x)
for different values of f
! We can extract the common code by defining a function sum
:
def sum(f: Int => Int, a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sum(f, a + 1, b)
The type Int => Int
is the type of functions that take an argument of type Int
and return results of type Int
. So sum
is a function which takes another function as a parameter.
Using sum, we can now rewrite our three summing functions:
def sumInts(a: Int, b: Int): Int = sum(id, a, b)
def sumSquares(a: Int, b: Int): Int = sum(square, a, b)
def sumPowersOfTwo(a: Int, b: Int): Int = sum(powerOfTwo, a, b)
2. The Collections Library
Scala offers Sequences, Sets and Maps in different flavors. Immutable collections are the default! Modifications are not applied in place, but a new collections is created (using structural sharing).
Scala collections have a huge number of higher-order functions already built-in, so you can do powerful transformations in a few lines of code.
map
– apply a function to each element of the collectionfoldLeft/foldRight
– repeatedly apply a function to the next element and the result accumulated so farfilter
– remove the elements that do not match the predicatetake/drop
– take or drop a number of elementstakeWhile/dropWhile
– take or drop elements until a condition is metcontains
– check if the collection contains an elementzip
– combine two sequences to a sequence of pairsslide
– divide a collection using a sliding windowgroupBy
– create a map grouping values by keys
And of course mkString
– format the collection to a String, separating the elements with a given separator. I have needed this (and programmed it myself) in Java countless times..
$ List(1,2,3,4,5).mkString(", ")
1, 2, 3, 4, 5
As the use of immutable collections (also sometimes referred to as persistent collections) is encouraged, you might wonder how to add or remove elements. It’s simple – the methods for this will give you a new collection, leaving the original one unchanged:
$ val v1 = Vector(1,2,3)
Vector(1, 2, 3)
$ v1 :+ 4
Vector(1, 2, 3, 4)
$ v1
Vector(1, 2, 3)
3. Traits
Just like in Java, any Scala class has exactly one superclass. And like a Java class can implement multiple interfaces, a Scala class can implement multiple traits. But as opposed to Java interfaces, traits can also contain implementations and state! A typical use case for this is the use as mix-ins for cross-cutting concerns. Supposed I need to inherit from Observable to implement a cross-cutting concern..
class MySpecificModel extends Observable {
...
}
.. but I also want to inherit within my domain model
class MySpecificModel extends MyGeneralModel {
...
}
How to adhere to both? In Java, interface inheritance is of course the solution (or would be, if Observable was an interface):
class MySpecificModel extends MyGeneralModel implements Observable {
...
}
But now we would have to implement addObserver, notifyObservers, .. over and over again. What about “Don’t Repeat Yourself”?
In Scala, we can mix-in traits, including implementation:
trait Observable {
def addObserver = { .. implementation ..}
def notifyObservers = { .. implementation ..}
}
class MySpecificModel extends MyGeneralModel with Observable {
...
}
Another great use case for traits with implementation is to provide “rich interfaces”. The trait Ordered
for example defines the methods <
, >
, <=
, >=
.
If it were a Java interface, the programmer would have to provide implementations for all four of them. In Scala, implementations for these are provided in the trait itself, and you only need to implement a single method (compare
).
4. Type Inference
In Scala, if you don’t state the type of a value or function, the compiler will attempt to infer it. This does not work everywhere, you still have to state the types for method parameters and some other places, but even so, it saves you a tremendous amount of typing. And more importantly, it frees your source code from a lot of boilerplate and makes it much more readable. Without sacrificing strong typing and compile-time type checking.
object Inference {
val x = 1 + 2 * 3
// the type of x is Int
val y = x.toString()
// the type of y is String
def succ(x: Int) = x + 1
// method succ returns Int values
}
5. Optional
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.. (Tony Hoare)
In Java there is no way to tell from the API if a method might return null or not. This leads to excessive null checking – or NullPointerExceptions. In Scala, you can state explicitly that a result may not always be provided.
scala> val m = Map(0 -> false, 1 -> true)
scala> m.get(0)
res0: Option[Boolean] = Some(false)
scala> m.get(5)
res1: Option[Boolean] = None
This gives you the opportunity to nicely compose calls, even if you might encounter missing values. You can chain calls that return optional values by applying higher-order functions.
scala> val m2 = Map(false -> "n", true -> "y")
scala> m2.get(true).map(x => x.toUpperCase)
6. Better Value Objects With Case Classes
Value objects are objects that serve as a structured value, to encapsulate domain data (a.k.a. data transfer object, record, …).
What you want in every Java class that’s used in that way is equals(), hashCode() and toString() methods based exclusively on all of the fields’ values. Of course you need a way to set the values (ideally only once, in the constructor, to create an immutable object), and a way to read them. So a typical Java class for an immutable value object might look like this:
public class Product implements java.io.Serializable {
private static final long serialVersionUID = 5120302881189308206L;
private final String name;
public Product(final String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public boolean equals(final Object other) {
if (other == null) {
return false;
}
if (this == other) {
return true;
}
if (!(other instanceof Product)) {
return false;
}
final Product otherProduct = (Product) other;
return new EqualsBuilder().append(name, other.name).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(177, 47).append(name).toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this).append("name", name).toString();
}
}
At some point during the development, you realize that you’d like to capture more information then just the name. So you add another field. No problem, all you have to do is:
- Add the new field to list of instance variables
- Add the new parameter to the constructor arguments
- Assign the constructor argument to the instance variable
- Create a “getter” method for the new field
- Add the new field to the equals() method
- Add the new field to the hashCode() method
- Add the new field to the toString() method
In Scala however, the same class would be written like this:
case class Product(name: String)
Yes, that’s all. To add another field to it, you have to follow these steps:
- Add the new field to the parameter list
No, I didn’t forget any. It’s really just one.
The case
keyword indicates that this is meant to be a value object, and the compiler will take care of the methods we need, and generate them to use the field values. By making working with immutable value objects convenient and easy, Scala encourages a functional programming style that relies on immutable values – which is good!
By the way, as a little bonus, you also get a copy method…
case class Customer(name: String, address: String, city: String)
val customer1 = Customer("Hans Meier", "Lehmweg 12", "Hamburg")
val customer2 = customer1.copy(name = "Ida Meier")
… and you can use them in pattern matching (see below)!
customer match {
case Customer(_, _, "Hamburg") => local
case _ => guest
}
So with case classes you have much less code to maintain, you don’t have to remember to make fields final, and you no longer need Apache Commons or Guava libraries for EqualsBuilder
, HashCodeBuilder
and ToStringBuilder
.
7. Pattern Matching
In Java, you have switch
statements:
int month = 10;
switch (month) {
case 1: return "January";
case 2: return "February";
case 3: return "March";
case 4: return "April";
...
In its simplest form, a Scala pattern match will look pretty much the same, only it will yield a value:
val month = 10;
month match {
case 1 => "January"
case 2 => "February"
case 3 => "March"
case 4 => "April"
...
}
Plus, you can also match on types…
something match {
case a: Int => "It's an int"
case b: String => "It's a string"
case _ => "It's neither an int nor a string"
}
… and sequences …
someSeq match {
case head +: tail => "more than one"
case head +: Nil => "one"
case Nil => "empty"
}
… and case classes (see above) …
customer match {
case Customer(_, _, "Hamburg") => local
case _ => guest
}
… and bind variables in matches …
customer match {
case Customer(name, _, "Hamburg") => s"$name is a local"
case Customer(name, _, city) => s"$name is a visitor from $city"
}
… and match alternatives …
month match {
case 3 | 4 | 5 => "Spring"
case 6 | 7 | 8 => "Summer"
...
}
… and use guards in matches …
case class Day(month: Int, day: Int)
d match {
case Day(3,x) if x > 20 => “Spring”
case _ => "..."
}
… and match regular expressions.
val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r<
To extract the capturing groups when a Regex is matched, use it as an extractor in a pattern match:
"2015-11-23" match {
case date(year, month, day) => s”The year is $year."
}
To check only whether the Regex matches, ignoring any groups, use a sequence wildcard:
"2015-11-23" match {
case date(_*) => "It's a date!"
}
So, pattern matching is a bit like switch statements… just infinitely more powerful!
8. Java Interoperability
You’ll not be missing any third party library that you so heavily relied on in Java – you can use any Java class in Scala:
val url = new java.net.URL("http://scala-lang.org")
val uuid = java.util.UUID.randomUUID().toString
You can even use Scala to implement Java interfaces…
class MyRunnable extends Runnable {
def run(): Unit = println("i am a runnable")
}
new Thread(new MyRunnable).start()
… and extend Java classes:
class MyThread extends Thread {
override def run(): Unit = println("i am a thread")
}
(new MyThread).run()
And Scala comes with a set of converters to turn Java collections into Scala collections – and vice versa:
val javaList = new java.util.ArrayList[String]()
javaList.add("hello")
javaList.add("world")
import scala.collection.JavaConverters._
val scalaList: scala.collection.mutable.Buffer[String] = javaList.asScala.map(_.toUpperCase)
println(s"scalaList = $scalaList")
val newJavaList: java.util.List[String] = scalaList.asJava
println(s"newJavaList = $newJavaList")
9. For-Expressions
Scala’s for expressions have nothing to do with a Java for loop. The ingredients of Scala’s for expressions are generators and filters:
// generator
scala> for (n <- 1 to 10 if n % 2 == 0) println(n)
And they can yield a value!
// collect result
val ns = for (n <- 1 to 10 if n % 2 == 0) yield n
ns = Vector(2, 4, 6, 8, 10)
You can have nicely readable nested loops, by using multiple generators:
for {
m <- 1 to 10
n <- 1 to 10
} yield (m * n)
And they don’t only work on collections.. remember Option (see above):
val m1 = Map(...)
for {
a <- m1.get(0)
b <- m2.get(a)
} yield b
And also for Try, Future… but that we’ll look at some other time.
10. Everything is an Expression
Programming languages distinguish between statements and expressions. Statements do not return results and are executed solely for their side effects, while expressions always return a result and often do not have side effects at all.
A statement-oriented, side-effecting style would look like this:
order.calculateTaxes()
order.updatePrices()
While these are expressions, yielding a value:
val tax = calculateTax(order)
val price = calculatePrice(order)
Functional languages are usually expression-oriented, and Scala is, too, so whenever you write a line or block of code in Scala, it will evaluate to something. This is true even for places where you might not expect this if you come from a statement-oriented, imperative language like Java, such as for-expressions or pattern matches:
val result1 = for {
m <- 1 to 10
n <- 1 to 10
} yield (m * n)
val month = m match {
case 1 => "January"
case 2 => "February"
case 3 => "March"
case _ => "Other"
}
Ten Features FTW
If you haven’t developed in Scala so far, and have read through this little Top Ten list above – does it give you the impression Scala is a complicated language?
I went through this list with groups of Java developers on several occassions, some of which had previously been put off by Scala’s reputation of being somewhat difficult.
Well, Scala is a big toolbox, and there is a lot to learn if you’re interested, but the moral of this post is:
If you switch from Java to Scala, and you only use the 10 features listed above, you’ve already made a huge step towards writing better programs, and having more fun while doing so.
So why not start with that? Move to Scala for just a few of its powerful features, that are easy to comprehend and already give you great benefit. From there, you move on, and explore the rest of the Scala world based on a solid foundation.
Java 8 Update
The list was written quite a while before Java 8 became mainstream. Java 8, after a long wait, brought some features to the Java language that narrow the gap a little, namely the introduction of Lambdas and Optional.
I decided to leave the list as it is, anyway, as for me personally the arguments still hold – the support for functional programming in Scala just feels much more refined and complete as it does in Java. If you think differently, just ignore items 1 and 5, and you’ll still be left with eight good reasons to make the move to Scala. For those who really need it to be ten – of course there are many more features in the Scala language that could be listed..
And Even More
I have a couple more features in the same “simple, but powerful” category that didn’t make it into the list. And after I published this post, some readers pointed out some of their favorite Scala features to me. So I’ll try to follow this up soon with another post: “10 More Scala Features Java Developers Love”. Stay tuned.
If you'd like to learn Scala and learning from a book is what works for you, I recommend to get one of these two:
Programming in Scala by Martin Odersky et al. | |
Programming Scala by Dean Wampler |