Blending OOP and FP: comparing Java 8 and Scala - Beyond Java 8 - Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Part 4. Beyond Java 8

Chapter 15. Blending OOP and FP: comparing Java 8 and Scala

This chapter covers

· An introduction to Scala

· How Java 8 relates to Scala and vice versa

· How functions in Scala compare to Java 8

· Classes and traits

Scala is a programming language that mixes object-oriented and functional programming. It’s often seen as an alternative language to Java for programmers who want functional features in a statically typed programming language that runs on the JVM while keeping a Java feel. Scala introduces many more features compared to Java: a more sophisticated type system, type inference, pattern matching (as presented in section 14.4), constructs to simply define domain specific languages, and so on. In addition, you can access all Java libraries within Scala code.

You may be wondering why we have a chapter about Scala in a Java 8 book. This book has been largely centered on adopting functional-style programming in Java. Scala, just like Java 8, supports the concepts of functional-style processing of collections (that is, stream-like operations), first-class functions, and default methods. But Scala pushes these ideas further: it provides a larger set of features around these ideas compared to Java 8. We believe you may find it interesting to compare Scala with the approach taken by Java 8 and see Java 8’s limitations. This chapter aims to shed light on this matter to appease your curiosity.

Keep in mind that the purpose of this chapter is not to teach you how to write idiomatic Scala code or everything about Scala. There are many features such as pattern matching, for comprehensions and implicits supported in Scala but not in Java, that we won’t discuss. Rather, this chapter focuses on comparing the new Java 8 features to what Scala provides, so you have an idea of the bigger picture. For example, you’ll find that you can write more concise and readable code in Scala compared to Java.

This chapter starts with an introduction to Scala: how to write simple programs and working with collections. Next, we discuss functions in Scala: first-class functions, closures, and currying. Finally, we look at classes in Scala and a feature called traits: Scala’s take on interfaces and default methods.

15.1. Introduction to Scala

This section briefly introduces basic Scala features so you can get a feel for simple Scala programs. We start with a slightly modified “Hello world” example written in an imperative style and a functional style. We then look at some data structures that Scala supports—List, Set, Map,Stream, Tuple, and Option—and compare them to Java 8. Finally, we present traits, Scala’s replacement for Java’s interfaces, which also support inheritance of methods at object-instantiation time.

15.1.1. Hello beer

Let’s look at a simple example so you get a feel for Scala’s syntax and features and how they compare to Java. To change a bit from the classic “Hello world” example, let’s bring in some beer. You want to print the following output on the screen:

Hello 2 bottles of beer

Hello 3 bottles of beer

Hello 4 bottles of beer

Hello 5 bottles of beer

Hello 6 bottles of beer

Imperative-style Scala

Here’s how the code to print this output looks in Scala using an imperative style:

Information on how to run this code can be found on the official Scala website.[1] This program looks quite similar to what you’d write in Java. It has a structure similar to Java programs: it consists of one method called main, which takes an array of strings as argument (type annotations follow the syntax s : String instead of String s like in Java). The main method doesn’t return a value, and so it’s not necessary to declare a return type in Scala as you’d have to do in Java using void.

1 See http://www.scala-lang.org/documentation/getting-started.html.

Note

In general, nonrecursive method declarations in Scala don’t need an explicit return type because Scala can infer it for you.

Before we look at the body of the main method, we need to discuss the object declaration. After all, in Java you have to declare the method main within a class. The declaration object introduces a singleton object: it declares a class Beer and instantiates it at the same time. Only one instance is ever created. This is the first example of a classical design pattern (the singleton design pattern) implemented as a language feature—free to use out of the box! In addition, you can view methods within an object declaration as being declared as static; this is why the signature of the main method isn’t explicitly declared as static.

Let’s now look at the body of main. It also looks similar to Java, but statements don’t need to end with a semicolon (it’s optional). The body consists of a while loop, which increments a mutable variable, n. For each new value of n you print a string on the screen using the predefined method println. The println line showcases another feature available in Scala: string interpolation. String interpolation allows you to embed variables and expressions directly in string literals. In the previous code you can use the variable n directly in the string literal s"Hello ${n} bottles of beer". Prepending the string with the interpolator s provides that magic. Normally in Java you’d have to do an explicit concatenation such as "Hello " + n + " bottles of beer".

Functional-style Scala

But what can Scala really offer after all our talk about functional-style programming throughout this book? The previous code can be written in a more functional-style form as follows in Java 8:

public class Foo {

public static void main(String[] args) {

IntStream.rangeClosed(2, 6)

.forEach(n -> System.out.println("Hello " + n +

" bottles of beer"));

}

}

Here’s how it looks in Scala:

object Beer {

def main(args: Array[String]){

2 to 6 foreach { n => println(s"Hello ${n} bottles of beer") }

}

}

It looks similar to the Java code but is less verbose. First, you can create a range using the expression 2 to 6. Here’s something cool: 2 is an object of type Int. In Scala everything is an object; there’s no concept of primitive types like in Java. This makes Scala a complete object-oriented language. An Int object in Scala supports a method named to, which takes as argument another Int and returns a range. So you could have written 2.to(6) instead. But methods that take one argument can be written in an infix form. Next, foreach (with a lowercase e) is similar toforEach in Java 8 (with an uppercase E). It’s a method available on a range (here you use the infix notation again), and it takes a lambda expression as argument to apply on each element. The lambda expression syntax is similar to Java 8 but the arrow is => instead of ->.[2] The previous code is functional: you’re not mutating a variable as you did in our earlier example using a while loop.

2 Note that in Scala the terminology “anonymous functions” or “closures” (interchangeable) is used to refer to what Java 8 calls lambda expressions.

15.1.2. Basic data structures: List, Set, Map, Tuple, Stream, Option

Feeling good after a couple of beers to quench your thirst? Most real programs need to manipulate and store data, so let’s now look at how you can manipulate collections in Scala and how that compares to Java 8.

Creating collections

Creating collections in Scala is simple, thanks to its emphasis on conciseness. To exemplify, here’s how to create a Map:

val authorsToAge = Map("Raoul" -> 23, "Mario" -> 40, "Alan" -> 53)

Several things are new with this line of code. First, it’s pretty awesome that you can create a Map and associate a key to a value directly, using the syntax ->. There’s no need to add elements manually like in Java:

Map<String, Integer> authorsToAge = new HashMap<>();

authorsToAge.put("Raoul", 23);

authorsToAge.put("Mario", 40);

authorsToAge.put("Alan", 53);

There are discussions to add similar syntactic sugar in future versions of Java, but it’s not available in Java 8.[3] The second new thing is that you can choose not to annotate the type of the variable authorsToAge. You could have explicitly written val authors-ToAge : Map[String,Int], but Scala can infer the type of the variable for you. (Note that the code is still statically checked! All variables have a given type at compile time.) We’ll come back to this feature later in the chapter. Third, you use the val keyword instead of var. What’s the difference? The keywordval means that the variable is read-only and can’t be reassigned to (just like final in Java). The var keyword means the variable is read-write.

3 See http://openjdk.java.net/jeps/186.

What about other collections? You can create a List (a singly linked list) or a Set (no duplicates) as easily:

val authors = List("Raoul", "Mario", "Alan")

val numbers = Set(1, 1, 2, 3, 5, 8)

The authors variable will have three elements and the numbers variable will have five elements.

Immutable vs. mutable

One important property to keep in mind is that the collections created previously are immutable by default. This means they can’t be changed after they’re created. This is useful because you know that accessing the collection at any point in your program will always yield a collection with the same elements.

So how can you update an immutable collection in Scala? To come back to the terminology used in the previous chapter, such collections in Scala are said to be persistent: updating a collection produces a new collection that shares as much as possible with its previous version, which persists without being affected by changes like we showed in figures 14.3 and 14.4. As a consequence of this property, your code will have fewer implicit data dependences: there’s less confusion about which location in your code updates a collection (or any other shared data structure) and at what point in time.

Let’s look at an example to demonstrate this idea. Let’s add an element to a Set:

In this example, the set of numbers isn’t modified. Instead, a new Set is created with an additional element.

Note that Scala doesn’t force you to use immutable collections—it just makes it easy to adopt immutability in your code. There are also mutable versions available in the package scala.collection.mutable.

Unmodifiable vs. immutable

Java provides several ways to create unmodifiable collections. In the following code the variable newNumbers is a read-only view of the set numbers:

Set<Integer> numbers = new HashSet<>();

Set<Integer> newNumbers = Collections.unmodifiableSet(numbers);

This means you won’t be able to add new elements through the newNumbers variable. But an unmodifiable collection is just a wrapper over a modifiable collection. This means that you could still add elements by accessing the numbers variable!

By contrast, immutable collections guarantee that nothing can change the collection, regardless of how many variables are pointing to it.

We explained in chapter 14 how you could create a persistent data structure: an immutable data structure that preserves the previous version of itself when modified. Any modifications always produce a new updated structure.

Working with collections

Now that you’ve seen how to create collections, you need to know what you can do with them. It turns out that collections in Scala support operations similar to what the Streams API provides. For instance, you’ll recognize filter and map in the following example and as illustrated infigure 15.1:

Figure 15.1. Stream-like operations with Scala’s List

val fileLines = Source.fromFile("data.txt").getLines.toList()

val linesLongUpper

= fileLines.filter(l => l.length() > 10)

.map(l => l.toUpperCase())

Don’t worry about the first line; it basically transforms a file into a list of strings consisting of the lines in the file (similar to what Files.readAllLines provides in Java 8). The second line creates a pipeline of two operations:

· A filter operation that selects only the lines that have a length greater than 10

· A map operation that transforms these long lines to uppercase

This code can be also written as follows:

val linesLongUpper

= fileLines filter (_.length() > 10) map(_.toUpperCase())

You use the infix notation as well as the underscore (_), which is a placeholder that’s positionally matched to any arguments. In this case you can read _.length() as l => l.length(). In the functions passed to filter and map, the underscore is bound to the line parameter that is to be processed.

There are many more useful operations available in Scala’s collection API. We recommend taking a look at the Scala documentation to get an idea.[4] Note that it’s slightly richer than what the Streams API provides (for example, there’s support for zipping operations, which let you combine elements of two lists), so you’ll definitely gain a few programming idioms by checking it out. These idioms may also make it into the Streams API in future versions of Java.

4 A list of notable and other packages can be found at www.scala-lang.org/api/current/#package.

Finally, remember that in Java 8 you could ask for a pipeline to be executed in parallel by calling parallel on a Stream. Scala has a similar trick; you only need to use the method par:

val linesLongUpper

= fileLines.par filter (_.length() > 10) map(_.toUpperCase())

Tuples

Let’s now look at another feature that’s often painfully verbose in Java: tuples. You may want to use tuples to group people by their name and their phone number (here simple pairs) without declaring an ad hoc new class and instantiate an object for it: (“Raoul”, “+ 44 007007007”), (“Alan”, “+44 003133700”), and so on.

Unfortunately, Java doesn’t provide support for tuples. So you have to create your own data structure. Here’s a simple Pair class:

public class Pair<X, Y> {

public final X x;

public final Y y;

public Pair(X x, Y y){

this.x = x;

this.y = y;

}

}

And of course you need to instantiate pairs explicitly:

Pair<String, String> raoul = new Pair<>("Raoul", "+ 44 007007007");

Pair<String, String> alan = new Pair<>("Alan", "+44 003133700");

Okay, but how about triplets? How about arbitrary-sized tuples? It becomes really tedious and ultimately will affect the readability and maintenance of your programs.

Scala provides tuple literals, which means you can create tuples through simple syntactic sugar—just the normal mathematical notation:

val raoul = ("Raoul", "+ 44 887007007")

val alan = ("Alan", "+44 883133700")

Scala supports arbitrary-sized[5] tuples, so the following are all possible:

5 Tuples have a limitation of 23 elements maximum.

You can access the elements of the tuples by their positions using the accessors _1, _2 (starting at 1), for example:

Isn’t that much nicer than what you’d have to write in Java? The good news is that there are discussions about introducing tuple literals in future versions of Java (see chapter 16 for more discussion of this).

Stream

The collections we described so far, List, Set, Map, and Tuple, are all evaluated eagerly (that is, immediately). Of course by now you know that streams in Java 8 are evaluated on demand (that is, lazily). You saw in chapter 5 that because of this property streams can represent an infinite sequence without overflowing the memory.

Scala provides a corresponding lazily evaluated data structure called Stream too! But Streams in Scala provide more features than those in Java. Streams in Scala remember values that were computed so previous elements can be accessed. In addition, Streams are indexed so elements can be accessed by an index just like a list. Note that the trade-off for these additional properties is that Streams are less memory-efficient compared to Java 8’s streams, because being able to refer back to previous elements means the elements need to be “remembered” (cached).

Option

Another data structure that you’ll be familiar with is Option. It’s Scala’s version of Java 8’s Optional, which we discussed in chapter 10. We argued that you should use Optional when possible to design better APIs, in which just by reading the signature of a method users can tell whether or not they can expect an optional value. It should be used instead of null when possible to prevent null pointer exceptions.

You saw in chapter 10 that you could use Optional to return the insurance’s name of a person if their age is greater than a minimum age, as follows:

public String getCarInsuranceName(Optional<Person> person, int minAge) {

return person.filter(p -> p.getAge() >= minAge)

.flatMap(Person::getCar)

.flatMap(Car::getInsurance)

.map(Insurance::getName)

.orElse("Unknown");

}

In Scala you can use Option in a way similar to Optional:

def getCarInsuranceName(person: Option[Person], minAge: Int) =

person.filter(_.getAge() >= minAge)

.flatMap(_.getCar)

.flatMap(_.getInsurance)

.map(_.getName).getOrElse("Unknown")

You can recognize the same structure and method names apart from getOrElse, which is the equivalent of orElse in Java 8. You see, throughout this book you’ve learned new concepts that can be directly applied to other programming languages! Unfortunately, null also exists in Scala for Java compatibility reasons and its use is highly discouraged.

Note

In the previous code you wrote _.getCar (without parentheses) instead of _.getCar() (with parentheses). In Scala parentheses aren’t required when calling a method that takes no parameters.

15.2. Functions

Scala functions can be seen as a sequence of instructions that are grouped together to perform a task. They’re useful to abstract behavior and are the cornerstone of functional programming.

In Java, you’re familiar with methods: functions associated with a class. You’ve also seen lambda expressions, which can be considered anonymous functions. Scala offers a richer set of features around functions than Java does, which we look at in this section. Scala provides the following:

· Function types, syntactic sugar to represent the idea of Java function descriptors (that is, a notation to represent the signature of the abstract method declared in a functional interface), which we described in chapter 3

· Anonymous functions that don’t have the write restrictions on nonlocal variables that Java’s lambda expressions have

· Support for currying, which means breaking down a function that takes multiple arguments into a series of functions that take part of the arguments

15.2.1. First-class functions in Scala

Functions in Scala are first-class values. This means they can be passed around as parameters, returned as a result, and stored in variables, just like other values such as an Integer or a String. As we’ve shown you in earlier chapters, method references and lambda expressions in Java 8 can also be seen as first-class functions.

Let’s look at an example of how first-class functions work in Scala. Let’s say you have a list of strings representing tweets people are sending to you. You’d like to filter this list with different criteria, for example, tweets that mention the word Java or tweets that have a short length. You can represent these two criteria as predicates (functions that return a Boolean):

def isJavaMentioned(tweet: String) : Boolean = tweet.contains("Java")

def isShortTweet(tweet: String) : Boolean = tweet.length() < 20

In Scala you can pass these methods directly to the built-in filter as follows (just as you could pass them using method references in Java):

val tweets = List(

"I love the new features in Java 8",

"How's it going?",

"An SQL query walks into a bar, sees two tables and says 'Can I join you?'"

)

tweets.filter(isJavaMentioned).foreach(println)

tweets.filter(isShortTweet).foreach(println)

Now let’s inspect the signature of the built-in method filter:

def filter[T](p: (T) => Boolean): List[T]

You may wonder what the type of the parameter p means (here (T) => Boolean), because in Java you’d expect a functional interface! This is a new syntax that’s not available in Java. It describes a function type. Here it represents a function that takes an object of type T and returns aBoolean. In Java this is encoded as a Predicate<T> or Function<T, Boolean>. This is exactly the same signature as the methods isJava-Mentioned and isShortTweet so you can pass them as argument to filter. The Java 8 language designers decided not to introduce a similar syntax for function types in order to keep the language consistent with previous versions. (Introducing too much new syntax in a new version of the language is seen as too much additional cognitive overhead.)

15.2.2. Anonymous functions and closures

Scala also supports the concept of anonymous functions. They have a syntax similar to lambda expressions. In the following example you can assign to a variable named isLongTweet an anonymous function that checks whether a given tweet is long:

Now in Java, a lambda expression lets you create an instance of a functional interface. Scala has a similar mechanism. The previous code is syntactic sugar for declaring an anonymous class of type scala.Function1 (a function of one parameter), which provides the implementation of the method apply:

val isLongTweet : String => Boolean

= new Function1[String, Boolean] {

def apply(tweet: String): Boolean = tweet.length() > 60

}

Because the variable isLongTweet holds an object of type Function1, you can call the method apply, which can be seen as calling the function:

As in Java, you could do the following:

Function<String, Boolean> isLongTweet = (String s) -> s.length() > 60;

boolean long = isLongTweet.apply("A very short tweet");

To use lambda expressions, Java provides several built-in functional interfaces such as Predicate, Function, and Consumer. Scala provides traits (you can think of traits as interfaces for now until we describe them in the next section) to achieve the same thing: Function0 (a function with 0 parameters and a return result) up to Function22 (a function with 22 parameters), which all define the method apply.

Another cool trick in Scala is that you can call the method apply using syntactic sugar that looks more like a function call:

The compiler automatically converts a call f(a) into f.apply(a) and, more generally, a call f(a1, ..., an) into f.apply(a1, ..., an), if f is an object that supports the method apply (note that apply can have any number of arguments).

Closures

In chapter 3 we commented on whether lambda expressions in Java constitute closures. To refresh, a closure is an instance of a function that can reference nonlocal variables of that function with no restrictions. But lambda expressions in Java 8 have a restriction: they can’t modify the content of local variables of a method in which the lambda is defined. Those variables have to be implicitly final. It helps to think that lambdas close over values, rather than variables.

In contrast, anonymous functions in Scala can capture variables themselves, not the values to which the variables currently refer. For example, the following is possible in Scala:

But in Java the following will result in a compiler error because count is implicitly forced to be final:

We argued in chapters 7, 13, and 14 that you should avoid mutation when possible to make your programs easier to maintain and parallelizable, so use this feature only when strictly necessary.

15.2.3. Currying

In chapter 14 we described a technique called currying: where a function f of two arguments (x and y, say) is seen instead as a function g of one argument, which returns a function also of one argument. This definition can be generalized to functions with multiple arguments, producing multiple functions of one argument. In other words, you can break down a function that takes multiple arguments into a series of functions that take a subpart of the arguments. Scala provides a construct to let you easily curry an existing function.

To understand what Scala brings to the table, let’s first revisit an example in Java. You can define a simple method to multiply two integers:

static int multiply(int x, int y) {

return x * y;

}

int r = multiply(2, 10);

But this definition requires all the arguments to be passed to it. You can manually break down the multiply method by making it return another function:

static Function<Integer, Integer> multiplyCurry(int x) {

return (Integer y) -> x * y;

}

The function returned by multiplyCurry captures the value of x and multiplies it by its argument y, returning an Integer. This means you can use multiplyCurry as follows in a map to multiply each element by 2:

Stream.of(1, 3, 5, 7)

.map(multiplyCurry(2))

.forEach(System.out::println);

This will produce the result 2, 6, 10, 14. This works because map expects a Function as argument and multiplyCurry returns a Function!

Now it’s a bit tedious in Java to manually split up a function to create a curried form (especially if the function has multiple arguments). Scala has a special syntax to do this automatically. You can define the normal multiply method as follows:

def multiply(x : Int, y: Int) = x * y

val r = multiply(2, 10);

And here is its curried form:

Using the (x: Int)(y: Int) syntax, the multiplyCurry method takes two argument lists of one Int parameter. In contrast, multiply takes one list of two Int parameters. What happens when you call multiplyCurry? The first invocation of multiplyCurry with a single Int (the parameter x), multiplyCurry(2), returns another function that takes a parameter y and multiplies it with the captured value of x (here the value 2). We say this function is partially applied as explained in section 14.1.2, because not all arguments are provided. The second invocation multiplies x and y. This means you can store the first invocation to multiplyCurry inside a variable and reuse it:

In comparison with Java, in Scala you don’t need to manually provide the curried form of a function as done here. Scala provides a convenient function-definition syntax to indicate that a function has multiple curried argument lists.

15.3. Classes and traits

We now look at how classes and interfaces in Java compare to Scala. These two constructs are paramount to design applications. You’ll see that Scala’s classes and interfaces can provide more flexibility than what Java offers.

15.3.1. Less verbosity with Scala classes

Because Scala is also a full object-oriented language, you can create classes and instantiate them to produce objects. At its most basic form, the syntax to declare and instantiate classes is similar to Java. For example, here’s how to declare a Hello class:

class Hello {

def sayThankYou(){

println("Thanks for reading our book")

}

}

val h = new Hello()

h.sayThankYou()

Getters and setters

It becomes more interesting once you have a class with fields. Have you ever come across a Java class that purely defines a list of fields, and you’ve had to declare a long list of getters, setters, and an appropriate constructor? What a pain! In addition, you’ll often see tests for the implementation of each method. A large amount of code is typically devoted to such classes in Enterprise Java applications. Consider this simple Student class:

public class Student {

private String name;

private int id;

public Student(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

}

You have to manually define the constructor that initializes all fields, two getters, and two setters. A simple class now has more than 20 lines of code! Several IDEs and tools can help you generate this code, but your codebase still has to deal with a large amount of additional code that’s not very useful compared to real business logic.

In Scala, constructors, getters, and setters can be implicitly generated, which results in code with less verbosity:

15.3.2. Scala traits vs. Java 8 interfaces

Scala has another useful feature for abstraction called traits. They’re Scala’s replacement for Java’s interfaces. A trait can define both abstract methods and methods with a default implementation. Traits can also be multiple inherited just like interfaces in Java, so you can see them as similar to Java 8’s interfaces that support default methods. Traits can also contain fields like an abstract class, which Java 8 interfaces don’t support. Are traits just like abstract classes? No, because traits can be multiple inherited, unlike abstract classes. Java has always had multiple inheritance of types because a class can implement multiple interfaces. Now Java 8, through default methods, introduces multiple inheritance of behaviors but still doesn’t allow multiple inheritance of state, something permitted by Scala traits.

To show an example of what a trait looks like in Scala, let’s define a trait called Sized that contains one mutable field called size and one method called isEmpty with a default implementation:

You can now compose it at declaration time with a class, here an Empty class that always has size 0:

Interestingly, compared to Java interfaces, traits can also be composed at object instantiation time (but it’s still a compile-time operation). For example, you can create a Box class and decide that one specific instance should support the operations defined by the trait Sized:

What happens if multiple traits are inherited declaring methods with the same signatures or fields with the same names? Scala provides restriction rules similar to those you saw with default methods in chapter 9.

15.4. Summary

Following are the key concepts you should take away from this chapter:

· Both Java 8 and Scala combine object-oriented and functional programming features into one programming language; both run on the JVM and to a large extent can interoperate.

· Scala supports collection abstractions similar to Java 8—List, Set, Map, Stream, Option—but also supports tuples.

· Scala provides richer features around functions than Java 8: function types, closures that have no restrictions on accessing local variables, and built-in currying forms.

· Classes in Scala can provide implicit constructors, getters, and setters.

· Scala supports traits: interfaces that can include both fields and default methods.