Scala as a better Java

This one is for the Java developers who are curious about Scala. Maybe you have briefly looked at Scala and you were deterred by what at first sight looked like ugly syntax with strange, incomprehensible operators, or you think you have to become a functional programming wizard to understand it.

While the standard library has some methods with names that look like strange operators, you’re not required to use these (for most of them there’s an alternative with a more readable name, such as foldLeft instead of /:), and you don’t have to be a functional programming wizard to get benefits from Scala. A good way to get started is to use it as a better Java. In this post I’ll show some examples of how Scala improves upon Java.

You can use == to compare strings

One counter-intuitive feature of Java is the fact that you should normally not use == to compare strings, because == means reference equality, and in the case of strings it does not compare the content of two strings.

In Scala, == is not a built-in operator of the language, it’s just a method that can be overridden. Ofcourse, for class String it compares the content of two strings rather than checking if the two operands refer to the same String object.

Type inference

Java has limited type inference, but in Scala this goes much further. Most of the time you don’t have to specify the type of a value or variable or the return type of a method; the Scala compiler figures out the type automatically from the expression used to initialize the value or variable, or the body of the method. This means that your code will be shorter and easier to read, because there’s less that you have to think about when reading the code.

That said, it’s often a good idea to explicitly specify the return types of methods, because it isn’t always obvious what the return type of a method is.

Case classes

The most well-known pattern in Java is the Java Beans pattern – you write a class with some private fields, getter and setter methods and equals() and hashCode() methods if needed. In Java, you need quite a lot of lines of code to write such a simple class.

public class Person {

    private String name;
    private LocalDate birthDate;
    private List<Person> children;

    public String getName() {
        return name;
    }

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

    public LocalDate getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }

    public List<Person> getChildren() {
        return children;
    }

    public void setChildren(List<Person> children) {
        this.children = children;
    }

    @Override
    public boolean equals(Object o) {
		// ...
    }

    @Override
    public int hashCode() {
		// ...
    }
}

In Scala, you can do the same thing in just one line of code.

case class Person(var name: String, var birthDate: LocalDate, var children: List[Person])

Note that Scala encourages you to make classes immutable (and especially having mutable fields in a case class is a code smell in Scala), so it’s better to write it like this:

case class Person(name: String, birthDate: LocalDate, children: List[Person])

The getters, equals() and hashCode() methods and some other methods (for pattern matching) are automatically generated for a case class.

Pattern matching

Pattern matching is a little bit like a switch statement in Java, but much more powerful. Instead of comparing on a set of values, it can compare if the input matches a more complex pattern. Once you start programming in Scala you’ll discover the many ways in which this is useful, and this blog post is far too short to describe all the possibilities that pattern matching offers.

trait Expr

case class NumExpr(value: Double) extends Expr

case class AddExpr(left: Expr, right: Expr) extends Expr
case class SubExpr(left: Expr, right: Expr) extends Expr
case class MulExpr(left: Expr, right: Expr) extends Expr
case class DivExpr(left: Expr, right: Expr) extends Expr

object ExpressionEvaluator extends App {

  // Use pattern matching to recognise different kinds of expressions
  // and extract parts out of the expressions
  def evaluate(expr: Expr): Double = expr match {
    case NumExpr(n) => n

    case AddExpr(left, right) => evaluate(left) + evaluate(right)
    case SubExpr(left, right) => evaluate(left) - evaluate(right)
    case MulExpr(left, right) => evaluate(left) * evaluate(right)
    case DivExpr(left, right) => evaluate(left) / evaluate(right)

    case _ => sys.error("Unknown type of expression: " + expr)
  }

  println(evaluate(AddExpr(MulExpr(NumExpr(2.0), NumExpr(3.2)),
                           SubExpr(NumExpr(2.5), NumExpr(1.0)))))
}

Patterns can be arbitrarily deeply nested; in the evaluate method I could for example have added a case for expressions of a specific form:

// Match an expression of the form (e1 * e2) + n
case AddExpr(MulExpr(e1, e2), NumExpr(n)) => (evaluate(e1) * evaluate(e2)) + n

For comprehensions

Scala’s for expression is much more powerful than Java’s for statement. You can for example add filters and have nested loops in a single for expression, leading to shorter and more readable code than the equivalent in Java.

// Java: Find the names of the children of people whose name starts with "A"
List<String> names = new ArrayList<>();
for (Person p : persons) {
  if (p.getName().startsWith("A")) {
    for (Person c : p.getChildren()) {
      names.add(c.getName());
    }
  }
}

// Scala: Find the names of the children of people whose name starts with "A"
val names = for (p <- persons if p.name.startsWith("A"); c <- p.children)
              yield c.name

Ofcourse, in Java 8 you could write this in a shorter way using streams, but there would still be a lot more boilerplate than in Scala.

// Java 8: Find the names of the children of people whose name starts with "A"
List<String> names = persons.stream()
		.filter(p -> p.getName().startsWith("A"))
		.flatMap(p -> p.getChildren().stream())
		.map(Person::getName)
		.collect(Collectors.toList());

// Scala: Do the same with filter, flatMap, map
val names = persons.filter(p => p.name.startsWith("A"))
                   .flatMap(_.children).map(_.name)

Conclusion

In Scala, you need to write a lot less code to do the same thing as in Java, which makes you more productive, not only when writing, but also when reading code. Even if you don’t want to learn about functional programming, Scala’s powerful type system and the many other features that Scala offers, it can still make you more productive.

You may also like...