BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Java 8 vs Scala: a Feature Comparison

Java 8 vs Scala: a Feature Comparison

Leia em Português

This item in japanese

Lire ce contenu en français

Bookmarks

Introduction

With the release of JDK 8 planned for 2013, Oracle has a pretty fixed idea of what will be included. Speaking at QCon London earlier this year, Simon Ritter outlined the new features that will be part of JDK 8, which include modularity (Project Jigsaw), JRockit/Hotspot convergence, annotations on types, and Project Lambda.

From a language perspective, perhaps the most important change is Project Lambda, which includes support for lambda expressions, virtual extension methods and better support for multicore platforms in the form of parallel collections.

Most of these features are already available in many other JVM languages, including Scala. Moreover, many of the approaches taken in Java 8 are surprisingly similar to those used in Scala. As a consequence, playing with Scala is a good way of getting a feeling for what programming with Java 8 will be like.

In this article we will explore Java 8’s new features, using both Java’s proposed syntax and Scala. We will cover lambda expressions, higher-order functions, parallel collections and virtual extension methods aka traits. Besides this, we will provide insights into the new paradigms integrated in Java 8, such as functional programming.

The reader will experience how the new concepts incorporated in Java 8 - that are already available in Scala - are not mere bells and whistles, but could introduce a true paradigm shift, which will offer great possibilities and may profoundly change the way we write software.

Lambda Expressions/Functions

Java 8 will include lambda expressions, finally! Lambda expressions have been available in the form of Project Lambda since 2009. At that time, lambda expressions were still referred to as Java Closures. Before we jump into some code examples, we will explain why lambda expressions will be a very welcome tool in the Java programmers tool belt.

Motivation for Lambda Expressions

A common use of lambda expressions is in GUI development. In general, GUI programming resolves around connecting behaviour to events. For example, if a user presses a button (an event), your program needs to execute some behaviour. This might be the storage of some data in a datastore. In Swing, for example, this is done using ActionListeners:

class ButtonHandler implements ActionListener {
      public void actionPerformed(ActionEvent e) {
            //do something
      }
}

class UIBuilder {
      public UIBuilder() {
            button.addActionListener(new ButtonHandler());
      }
}

This example shows the use of the class ButtonHandler as a callback replacement. The class ButtonHandler is only there to hold a single method: actionPerformed, defined in the ActionListener interface. We could simplify this code a bit by using anonymous inner classes:

class UIBuilder {
      public UIBuilder() {
            button.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent event) {
                        //do something
                  }
            }
      }
}

This code is somewhat cleaner. When we look more closely at the code, we still create an instance of a class just to call a single method. These kinds of problems are exactly the ones solved by introducing lambda expressions.

Lambda Expressions as Functions

A lambda expression is a function literal. It defines a function with input parameters and function body. The syntax of the lambda expression in Java 8 is still under discussion, but will look something like this:

(type parameter) -> function_body

A concrete example is:

(String s1, String s2) -> s1.length() - s2.length();

This lambda expression calculates the difference in length between two strings. There are some extensions to this syntax, like avoiding the type definition for the arguments, as we will see later on, and supporting multi-line definitions by using { and } to group statements.

The Collections.sort() method would be an ideal usage example for this expression. It enables us to sort a collection of Strings based on their lengths:

List <String> list = Arrays.asList("looooong", "short", "tiny" );
Collections.sort(list, (String s1, String s2) -> s1.length() - s2.length());
> "tiny", "short", "looooong".

So, instead of feeding the sort method with an implementation of Comparator, as we would have to do with current Java, passing the above lambda expression is all that is needed to achieve the same result.

Lambda Expression as Closures

Lambda expressions have some interesting properties. One is that they are closures. A closure allows a function to access variables outside its immediate lexical scope.

String outer = "Java 8"
(String s1) -> s1.length() - outer.length()

The example shows that the lambda expression has access to the String outer, which is defined outside its scope. For inline usage scenarios closure can be very handy.

Type Inference also for Lambda Expressions

Type inference, which was introduced in Java 7, also applies to lambda expressions. Type inference, in a nutshell, means that a programmer can omit the type definition everywhere where the compiler can ‘infer’ or deduce the type by itself.

If type inference was used for the sorting lambda expression, it could be written as follows:

List<String> list = Arrays.asList(...);
Collections.sort(list, (s1, s2) -> s1.length() - s2.length());

As you can see, the types for the parameters s1 and s2 are omitted. Because the compiler knows that the list contains a collection of Strings it knows that a lambda expression used as comparator must also have two parameters of type String. Consequently, the types do not need to be declared explicitly, even though you are free to do so.

The main advantage of type inference is reduction of boilerplate. If the compiler can infer the type for us, why should we define them ourselves?

Hello Lambda Expressions, Goodbye Anonymous Inner Classes

Let’s see how lambda expressions and type inference help to simplify the callback example we discussed initially:

class UIBuilder {
  public UIBuilder() {
      button.addActionListener(e -> //process ActionEvent e)
  }
}

Instead of defining a class to hold our callback method, we now directly pass a lambda expression into the addActionListener method. Besides the reduction lot of boilerplate code and increased readability, it lets us directly express the only thing we are really interested in: handling the event.

Before we unravel more advantages of lambda expressions, we first take a look at the lambda expressions counterpart in Scala.

Lambda Expressions in Scala

Functions are the basic building blocks of a style of programming called functional programming. Scala combines object orientation, known from Java, and functional programming. In Scala, a lambda expression is a basic building block called a ‘function’ or ‘function literal’. Functions in Scala are first class citizens. They can be assigned to vals or vars (final or non final variables), they can be passed as an argument to another function, and they can be combined to form new functions.

In Scala a function literal is written as follows:

(argument) => //function body

For example, the previous Java lambda expression calculating the difference of length of two strings, is written in Scala like so:

(s1: String, s2: String) => s1.length - s2.length

In Scala, function literals are also closures. They can access variables defined outside their own lexical scope.

val outer = 10
val myFuncLiteral = (y: Int) => y * outer
val result = myFuncLiteral(2)
> 20

This example would result in 20. As you see, we assigned a function literal to a variable named myFuncLiteral.

The syntactical and semantic similarities between Java 8’s lambda expression and Scala’s function are remarkable. Semantically they are exactly the same, whereas syntactically the only differences are the symbol for the arrow (Java8: -> Scala: =>) and the shorthand notation, which is not covered here.

Higher-Order Functions as Reusable Building Blocks

The great advantage of function literals is that we can pass them around as any other literals, like a String or an arbitrary Object. This offers a wide range of possibilities and allows for highly compact and reusable code constructs.

Our First Higher-Order Function

When we pass a function literal to a method, we basically have a method that accepts a method. Such methods are called higher-order functions. The addActionListener method in the previous Swing code example is exactly one of those. We also can define our own higher-order functions, which can offer us a lot of benefit. Let’s look at a simple example:

def measure[T](func: => T):T = {
      val start = System.nanoTime()
      val result = func
      val elapsed = System.nanoTime() - start
      println("The execution of this call took: %s ns".format(elapsed))
      result
}

In this example, we have a measure method, which measures the time needed to execute the function literal callback called func. The signature of func is that it does not take any parameters and returns a result of the generic type T. As you can see, functions in Scala do not necessarily need parameters even though they can - and most often also will - have parameters.

Now we can pass any function literal (or method) in the measure method:

def myCallback = {
      Thread.sleep(1000)
      "I just took a powernap"
}

val result = measure(myCallback);
> The execution of this call took: 1002449000 ns

What we have done, from a conceptual point of view, is separating the concern of measuring the length of a method call from the actual computation. We created two reusable code constructs (the measure part and the callback part) that are very loosely coupled, similar to an interceptor.

Reuse through Higher-Order Functions

Let’s look at another hypothetical example where two reusable constructs are slightly tighter coupled:

def doWithContact(fileName:String, handle:Contact => Unit):Unit = {
  try{
      val contactStr = io.Source.fromFile(fileName).mkString
      val contact = AContactParser.parse(contactStr)
      handle(contact)
    } catch {
       case e: IOException => println("couldn't load contact file: " + e)
       case e: ParseException => println("couldn't parse contact file: " + e)
    }
}

The method doWithContact reads a contact from a file, such as a vCard or similar, and offers it to a parser which converts it to a contact domain object. The contact domain object is then passed to the function literal callback handle, which does with the contact domain object whatever the function dictates. The doWithContact method as well as the function literal returns the type Unit, which is the equivalent of a void method in Java.

Now we can define various callbacks that can be passed to the doWithContact method:

val storeCallback = (c:Contact) => ContactDao.save(c)

val sendCallback = (c:Contact) => {
      val msgBody = AConverter.convert(c)
      RestService.send(msgBody)
}

val combinedCallback = (c:Contact) => {
      storeCallback(c)
      sendCallback(c)
}

doWithContact("custerX.vcf", storeCallback)
doWithContact("custerY.vcf", sendCallback)
doWithContact("custerZ.vcf", combinedCallback)

The callback can also be passed inline:

doWithContact("custerW.vcf", (c:Contact) => ContactDao.save(c))

Higher-Order Functions in Java 8

The Java 8 equivalent would look very similar - using the current syntax proposal:

public interface Block<T> {
void apply(T t);
} public void doWithContact(String fileName, Block<Contact> block) {
try{
String contactStr = FileUtils.readFileToString(new File(fileName));
Contact contact = AContactParser.parse(contactStr);
block.apply(contact);
} catch(IOException e) {
System.out.println("couldn't load contact file: " + e.getMessage());
} catch(ParseException p) {
System.out.println("couldn't parse contact file: " + p.getMessage());
}
} //usage
doWithContact("custerX.vcf", c -> ContactDao.save(c))

Benefit of Higher-Order Functions

As you can see, functions helped us to cleanly separate the concern of creating a domain object from processing it. By doing so, new ways of handling contact domain objects can easily be plugged in without being coupled to the logic that creates the domain object.

As a result, the benefit we gain from higher-order functions is that our code stays DRY (Don’t Repeat Yourself) so that the programmer can optimally profit from code reuse on a very granular level.

Collections and Higher-Order Functions

Higher-order functions provide a very efficient way of dealing with collections. Because almost every program makes use of collections, efficient collection handling can be a great help.

Filtering Collections: Before and After

Let’s look at a common use case involving collections. Say we want to apply a calculation to every element of a collection. For example, we have a list of Photo objects and we want to filter out all Photos of a certain size.

List<Photo> input = Arrays.asList(...);
List<Photo> output = new ArrayList();
for (Photo c : input){
      if(c.getSizeInKb() < 10) {
            output.add(c);
      }
}

This code contains a lot of boilerplate, such as creating the result collection and adding the new elements to the list. An alternative is the use of a Function class, to abstract over function behaviour:

interface Predicate<T> {
    boolean apply(T obj);
}

Which results in code written like this using Guava

final Collection<Photo> input = Arrays.asList(...);
final Collection<Photo> output =
    Collections2.transform(input, new Predicate<Photo>(){
            @Override
            public boolean apply(final Photo input){
                  return input.getSizeInKb() > 10;
            }
    });

This code reduces the boilerplating somewhat, but it still is messy, verbose code. If we translate this code to Scala or Java 8, we get a taste of the power and elegance of lambda expressions.

Scala:

val photos = List(...)
val output = photos.filter(p => p.sizeKb < 10)

Java 8:

List<Photo> photos = Arrays.asList(...)
List<Photo> output = photos.filter(p -> p.getSizeInKb() < 10)

Both of these implementations are elegant, and very succinct. Note that they both make use of type inference: the function parameter p of type Photo is not explicitly defined. As you can see in Scala type inference is a standard feature.

Function Chaining in Scala

So far we have saved at least six lines of code and even improved readability. The fun starts when we chain several higher-order functions. To illustrate that, let’s create a Photo class and add additional properties in Scala:

case class Photo(name:String, sizeKb:Int, rates:List[Int])

Without knowing much Scala, what we have done is declare the class Photo with three instance variables, name, sizeKb and rates. The rates will contain the user ratings for this image, from 1 to 10. Now we can create an instance of the Photo class, which is done as follows:

val p1 = Photo("matterhorn.png", 344, List(9,8,8,6,9))
val p2 = ...
val photos = List(p1, p2, p3, ...)

With this list of Photos, it is now quite easy to define various queries by chaining multiple higher-order functions after each other. Suppose we have to extract the file names of all the pictures whose file size is greater than 10MB. The first question is how do we transform a list of Photos into a list of filenames? To achieve that we use one of the most powerful higher-order functions, called map:

val names = photos.map(p => p.name)

The map method transforms each element of a collection to the type defined in the function passed to it. In this example we have a function that receives a Photo object and returns a String, which is the filename of the Image.

By using map we can solve the given task by chaining the map method after the filter method:

val fatPhotos = photos.filter(p => p.sizeKb > 10000)
                      .map(p => p.name)

We do not have to be afraid of NullPointerExceptions, because each method (filter, map, etc.) always returns a collection, which can be empty but never null. So if the photos collection was empty right from the start, the result of the computation would still be an empty collection.

Function chaining is also referred to as ‘function composition’. Using function composition we can shop in the Collections API to look for the building blocks by which our problem can be solved.

Let’s consider a more advanced example:

Task: "Return the names of all photos whose average rating is higher than 6, sorted by the total amount of ratings given:"

val avg = (l:List[Int]) => l.sum / l.size
val minAvgRating = 6
val result = photos.filter(p => avg(p.ratings) >= minAvgRating)
                   .sortBy(p => p.ratings.size)
                   .map(p => p.name)

To achieve this task we rely on the sortBy method, which expects a function that takes the element type of the collection as input (here Photo) and returns an object of type Ordered (here an Int). Because a List does not offer an average method, we defined the function literal avg that calculates the average of the given List of Ints for us in the anonymous function passed to the filter method.

Function Chaining in Java 8

It is not clear yet which higher-order functions the collection classes of Java 8 will offer. Filter and map will most probably be supported. Consequently, the first shown chained example will likely appear in Java 8 as follows:

List<Photo> photos = Arrays.asList(...)
List<String> output = photos.filter(p -> p.getSizeInKb() > 10000)
                           .map(p -> p.name)

Again, it is remarkable that there is almost no syntactic difference compared to the Scala version.

Higher-order functions in combination with collections are extremely powerful. They are not only very concise and readable, but also save us a lot of boilerplate code, with all the benefits thereof, like fewer tests and fewer bugs. However, there’s more...

Parallel Collections

So far we have not touched one of the most important advantages of higher-order functions in collections. Besides conciseness and readability, higher-order functions add a very important layer of abstraction. In all the previous examples we haven’t seen any loops. Not even once have we had to iterate over a collection to filter, map or sort elements. Iterating is hidden from the user of the collection, or in other words abstracted away.

This additional abstraction layer is key to leveraging multicore platforms, because the underlying implementation of the loop can choose by itself how to iterate over the collection. Consequently, iterating can not only be done sequentially but also in parallel. With the advance of multicore platforms, leveraging parallel processing is no longer a nice-to-have. Today’s programming languages need to be able to cope with the demands of parallel platforms.

In theory, we all could write our own parallel code. In practice that’s not a good idea. First of all, writing robust parallel code, especially with shared state and intermediate results that need to be merged, is extremely hard. Second, ending up with many different implementations wouldn’t be desirable. Even though Java7 has Fork/Join, the problem to decompose and reassemble the data is left to the client, which is not the abstraction level we are looking for. And third, why bother if we already have the answer in the form of functional programming?

Thus, let very smart people write the parallel iteration code once and abstract over their usage by means of higher-order functions.

Parallel Collections in Scala

Let’s look at a simple Scala example, which makes use of parallel processing:

def heavyComputation = "abcdefghijk".permutations.size
(0 to 10).par.foreach(i => heavyComputation)

We first define the method heavyComputation, which performs - well - a heavy computation. On a quad core laptop this expression takes about 4 seconds to perform. We then instantiate a range collection (0 to 10) and invoke the par method. The par method returns a parallel implementation, which offers the exact same interfaces as its sequential counterpart. Most Scala collection types have such a par method. From a usage point of view that’s all there is to it.

Let’s see what performance gains we can achieve on a quad core computer. In order to gain insights we reuse the measure method from further above:

//single execution
measure(heavyComputation)
> The execution of this call took: 4.6 s

//sequential execution
measure((1 to 10).foreach(i => heavyComputation))
> The execution of this call took: 46 s

//parallel execution
measure((1 to 10).par.foreach(i => heavyComputation))
> The execution of this call took: 19 s

What might be surprising at first glance is that the parallel execution is only about 2.5 times as fast, even though we are using four cores. The reason is that parallelism comes with the price of additional overhead, in the form of threads that need to be started and intermediate results that need to merged. Therefore, it is not a good idea to use parallel collections by default, but only apply them for known heavy computations.

Parallel Collections in Java 8

In Java 8 the proposed interface for parallel collection again is almost identical to Scala:

Array.asList(1,2,3,4,5,6,7,8,9.0).parallel().foreach(int i ->
heavyComputation())

The paradigm is exactly the same as in Scala, with the sole difference that the method name to create a parallel collection is parallel() instead of par.

All at Once: A Larger Example

To recap higher-order functions / lambda expression in combination with parallel collections, let’s look at a larger example in Scala, which combines many of the concepts introduced in the sections above.

For this example we chose a random site, which offers a great variety of beautiful nature wallpapers. We will write a program, that strips all the wallpaper image urls from this page and downloads the images in parallel. Besides the Scala core libraries, we use two other ones, Dispatch for http communication and Apache’s FileUtils, to simplify certain tasks. Be prepared to see some Scala concepts that were not explained in this article, but whose intention should be more or less understandable.

import java.io.File
import java.net.URL
import org.apache.commons.io.FileUtils.copyURLToFile
import dispatch._
import dispatch.tagsoup.TagSoupHttp._
import Thread._

object PhotoScraper {

  def main(args: Array[String]) {
      val url = "http://www.boschfoto.nl/html/Wallpapers/wallpapers1.html"
      scrapeWallpapers(url, "/tmp/")
  }

  def scrapeWallpapers(fromPage: String, toDir: String) = {
      val imgURLs = fetchWallpaperImgURLsOfPage(fromPage)
      imgURLs.par.foreach(url => copyToDir(url, toDir))
  }

  private def fetchWallpaperImgURLsOfPage(pageUrl: String): Seq[URL] = {
      val xhtml = Http(url(pageUrl) as_tagsouped)
      val imgHrefs = xhtml \\ "a" \\ "@href"
      imgHrefs.map(node => node.text)
              .filter(href => href.endsWith("1025.jpg"))
              .map(href => new URL(href))
  }

  private def copyToDir(url: URL, dir: String) = {
     println("%s copy %s to %s" format (currentThread.getName, url, dir))
     copyURLToFile(url, new File(toDir, url.getFile.split("/").last))
    }

Code explanation

The scrapeWallpapers method processes the control flow, which is fetching the image URLs from the html and downloading each of them.

By means of the fetchWallpaperImgURLsOfPage, all the wallpaper image URLs are screen scraped from the html.

The Http object is a class of the HTTP dispatch library, which provides a DSL around Apache’s httpclient library. The method as_tagsouped converts html in xml, which is a built-in datatype in Scala.

val xhtml = Http(url(pageUrl) as_tagsouped)

From the html, in the form of xhtml, we then retrieve the relevant hrefs of the images we want to download:

val imgHrefs = xhtml \\ "a" \\ "@href"

Because XML is native in Scala we can use the xpath-like expression \\ to select the nodes we are interested in. After we have retrieved all hrefs we need to filter out all image URLs and convert the hrefs to URL objects. In order to achieve that, we chain several higher-order functions of Scala’s Collection API like map and filter. The result is a List of image URLs.

imgHrefs.map(node => node.text)
        .filter(href => href.endsWith("1025.jpg"))
        .map(href => new URL(href))

The next step is to download each image in parallel. To achieve parallelism, we turn the list of image names into a parallel collection. Consequently, the foreach method starts several threads to loop through the collection simultaneously. Each thread will eventually call the copyToDir method.

imgURLs.par.foreach(url => copyToDir(url, toDir))

The copyToDir method makes use of Apache Common’s FileUtils. The static method copyURLToFile of the FileUtil class is statically imported and therefore can be called directly. For clarity we also print the name of the thread that performs the task. When executed in parallel, it will illustrate that multiple threads are busy processing.

private def copyToDir(url: URL, dir: String) = {
    println("%s copy %s to %s" format (currentThread.getName, url, dir))
    copyURLToFile(url, new File(toDir, url.getFile.split("/").last))
}

This method also illustrates that Scala is fully interoperable with existing Java libraries.

Scala’s functional features, and the resulting benefits such as higher-order functions on collections and the "free" parallelism, make it possible to accomplish parsing, IO and conversion of data in parallel with only a few lines of code.

Virtual Extension Methods/Traits

Virtual extension methods in Java are similar to ‘traits’ in Scala. What are traits, exactly? A trait in Scala offers an interface, and optionally includes an implementation. This structure offers great possibilities. This becomes clear when we start composing classes with traits.

Just like Java 8, Scala doesn’t support multiple inheritance. In both Java and Scala a subclass can only extend a single superclass. With traits however, inheritance is different. A class can "mix in" multiple traits. An interesting fact is that the class gains both the type and all methods and state of the mixed in trait(s). Traits are therefore also called mixins, as they mix in new behaviour and state into a class.

The question that remains is: if traits support some form of multiple inheritance, won’t we suffer from the notorious "diamondproblem"? The answer, of course, is no. Scala defines a clear set of precedence rules that determine when and what is executed within the multiple inheritance hierarchy. This is independent of the number of traits mixed in. These rules provide us with the full benefits of multiple inheritance without any of the problems associated with it.

If Only I Had a Trait

The following example shows a familiar code-snippet for Java developers:

class Photo {
  final static Logger LOG = LoggerFactory.getLogger(Photo.class);

  public void save() {
     if(LOG.isDebugEnabled()) {
         LOG.debug("You saved me." );
     }
     //some more useful code here ...
  }
}

Logging is thought of as a cross cutting concern, from a design perspective. However, this is hardly noticeable in the daily practice of Java development. Each class, again and again, declares a logger. We also keep checking whether a log level is enabled, using for example isDebugEnabled(). This is a clear violation of DRY: Don’t Repeat Yourself.

In Java, there is no way to validate that a programmer declares the log level check or use the proper logger associated with the class. Java developers got so used to this practice, it’s considered a pattern now.

Traits offer a great alternative to this pattern. If we put the logging functionality into a trait, we can mix-in this trait in any class we’d like. This enables access for the class to the cross-cutting concern ‘logging’, without limiting the possibilities of inheriting from another class.

Logging as a Trait, a Solution to the Logging Problem

In Scala, we would implement the Loggable trait like the following code snippet:

trait Loggable {
 self =>

 val logger = Slf4jLoggerFactory.getLogger(self.getClass())

 def debug[T](msg: => T):Unit = {
      if (logger.isDebugEnabled()) logger.debug(msg.toString)
  }
}

Scala defines a trait using the keyword ‘trait’. The body of a trait can contain anything an abstract class is allowed to contain, like fields and methods. Another interesting part in the logging example is the use of self =>. The logger should log the class that mixes in the Loggable trait, not Loggable itself. The self => syntax, called self-type in Scala, enables the trait to get a reference to the class that mixes it in.

Note the use of the parameterless function msg: => T as input parameter for the debug method. The main reason why we use the isDebugEnabled() check is to make sure that the String that is logged is only computed if the debug level is enabled. So if the debug method would only accept a String as input parameter, the log message would always get computed no matter whether the debug loglevel is enabled or not, which is not desirable. By passing the parameterless function msg: => T instead of a String however, we get exactly what we want: the msg function that will return the String to be logged is only invoked when the isDebugEnabled check succeeds. If the isDebugEnabled check fails the msg function is never called and therefore no unnecessary String is computed.

If we want to use the Loggable trait in the Photo class, we need to use extends to mix in the trait:

class Photo extends Loggable {
      def save():Unit = debug("You saved me");
}

The ‘extends’ keyword gives the impression that Photo inherits from Loggable, and therefore cannot extend any other class. This is not the case. The Scala syntax demands that the first keyword for mixing in or extending a class is ‘extends’. If we want to mix in multiple traits, we have to use the keyword ‘with’ for each trait after the first. We will see more examples using ‘with’ further below.

To show that this actually works, we call the method save() on Photo:

new Photo().save()
18:23:50.967 [main] DEBUG Photo - You saved me

Adding more Behaviours to your Classes

As we discussed in the previous paragraph, a class is allowed to mix in multiple traits. So besides logging, we can also add other behaviour to the Photo class. Let’s say we want to be able to order Photos based on their file size. Luckily for us, Scala offers a number of traits out of the box. One of these traits is the Ordered[T] trait. Ordered is similar to the Java interface Comparable. The big, and very important, difference is that the Scala version offers an implementation as well:

class Photo(name:String, sizeKb:Int, rates:List[Int]) extends Loggable with 
Ordered[Photo]{
      def compare(other:Photo) = {
            debug("comparing " + other + " with " + this)
            this.sizeKb - other.sizeKb
      }
      override def toString = "%s: %dkb".format(name, sizeKb)
}

In the example above, two traits are mixed in. Besides the previously defined Loggable trait, we also mix in the Ordered[Photo] trait. The Ordered[T] trait requires the implementation of the compare(type:T) method. This is still very similar to Java’s Comparable.

Besides the compare method, the Ordered trait also offers a number of different methods. These methods can be used to compare objects in various ways. They all make use of the implementation of the compare method.

val p1 = new Photo("Matterhorn", 240)
val p2 = new Photo("K2", 500)
p1 > p2
> false
p1 <= p2
> true

Symbolic names like > and <= etc. are not special reserved keywords in Scala like in Java. The fact that we can compare objects using >, <= etc. is due to the Ordered trait that implements the methods with these symbols.

Classes that implement the Ordered trait can be sorted by every Scala collection. By having a collection populated with Ordered objects, ‘sorted’ can be called on the collection, which sorts the objects according to the order defined in the compare method.

val p1 = new Photo("Matterhorn", 240)
val p2 = new Photo("K2", 500)
val sortedPhotos = List(p1, p2).sorted
> List(K2: 500kb, Matterhorn: 240kb)

The Benefits of Traits

The above examples reveal that we are able to isolate generic functionality in a modular fashion using traits. We can plug the isolated functionality in every class if needed. To equip the Photo class with logging functionality, we mixed in the Loggable trait, for ordering the Ordered trait. These traits are reusable in other classes.

Traits are a powerful mechanism to create modular and DRY (Don’t Repeat Yourself) code using built-in language features, without having to rely on extra technology complexity like aspect oriented programming.

The Motivation for Virtual Extension Methods

The Java 8 specification defines a draft for virtual extension methods. Virtual extension methods will add default implementations to new and/or existing methods of existing interfaces. Why is that?

For many existing interfaces it would be very beneficial to have support for lambda expressions in the form of higher-order functions. As an example, let’s consider the java.util.Collection interface. It would be desirable for the java.util.Collection interface to provide a forEach(lambdaExpr) method. If such a method was added to the interface without a default implementation, all implementing classes would have to provide one. It is self-evident that this would lead to a very challenging compatibility hell.

That’s why the JDK team came up with virtual extension methods. With this feature a forEach method, for example, could be added to java.util.Collection including a default implementation. Consequently, all implementing classes will automatically inherit this method and its implementation. By doing so, their API is able to evolve in a completely non-intrusive way, which is exactly what virtual extension methods intend to achieve. If the implementing class is not satisfied with the default implementation, it simply can override it.

Virtual Extension Methods vs Traits

The primary motivator for virtual extension methods is API evolution. A welcome side effect is that they offer a form of multiple inheritance, which is limited to behaviour. Traits in Scala not only provide multiple inheritance of behaviour, but also of state. Besides state and behaviour inheritance, traits offer a means to get a reference to the implementing class, shown with the ‘self‘ field in the Loggable example.

From a usage point of view, traits offer a richer set of features than virtual extension methods. However, their motivation is different: in Scala traits were always intended as modular building blocks that offer multiple inheritance ‘without the issues’, whereas virtual extension methods primarily have to enable API evolution and in the second place ‘multiple behaviour inheritance’.

The Loggable and Ordered Traits in Java 8

In order to see what we can achieve with virtual extension methods, let’s try to implement the Ordered and Loggable trait in Java 8.

The Ordered trait could be fully implemented with virtual extension methods, because no state is involved. As mentioned the counterpart of Scala’s Ordered trait is java.lang.Comparable in Java. The implementation would look as follows:

interface Comparable<T> {

      public int compare(T that);

      public boolean gt(T other) default {
       return compare(other) > 0
      }

      public boolean gte(T other) default {
       return compare(other) >= 0
      }

      public boolean lt(T other) default {
       return compare(other) < 0
      }

      public boolean lte(T other) default {
       return compare(other) <= 0
      }

}

We added new comparison methods to the existing Comparable interface (‘greater than’, ‘greater than or equals’, ‘less than’, ‘less than or equals’ identical to those found in the Ordered trait: >, >=, <, <=). The default implementation, marked with the default keyword, forwards all calls to the existing abstract compare method. The result is that an existing interfaces is enriched with new methods, without the need for classes implementing Comparable to implement these methods as well. The Ordered trait in Scala looks quite similar to this implementation.

If the Photo class implemented Comparable, we also would be able to perform comparison operations with those newly added methods:

Photo p1 = new Photo("Matterhorn", 240)
Photo p1 = new Photo("K2", 500)
p1.gt(p2)
> false
p1.lte(p2)
> true

The Loggable trait could not be fully implemented with virtual extension methods, but almost:

interface Loggable {
      final static Logger LOG = LoggerFactory.getLogger(Loggable.class);

      void debug(String msg) default {
            if(LOG.isDebugEnabled()) LOG.debug(msg)
      }
      void info(String msg) default {
            if(LOG.isInfoEnabled()) LOG.info(msg)
      }
      //etc...
}

In this example, we add log methods, like debug, info etc., to the Loggable interface, that by default delegate their calls to the static Logger instance. What we miss is a means to get a reference to the implementing class. Because this mechanism is lacking, we use the Loggable interface as logger, which would log all statements under Loggable instead of the implementing class. Because of this limitation, virtual extension methods are less suitable for such a usage scenario.

To sum up, traits and virtual extension methods both provide multiple inheritance for behaviour. Traits also provide multiple inheritance for state and a means to acquire a handle to the implementing class.

Conclusion

Java 8 is going to provide a variety of new language features, with the potential to fundamentally change the way we write applications. Especially the functional programming constructs, such as lambda expressions, can be considered a paradigm shift. This shift provides great new possibilities for more concise, compact and easy to understand code.

In addition, lambda expressions are key to enabling parallel processing.

As explained in this article all these features are already available in Scala. Developers who want to try them out can explore early builds of Java 8 on most platforms. Alternatively, we recommend taking a look at Scala as a way of preparing for the paradigm shifts to come.

Appendix

Information on Java 8 mainly stems from the following presentations:

Java 7 and 8: WhereWe'veBeen, WhereWe'reGoing

VirtualExtensionMethods

About the Authors

Urs Peter is Senior Consultant at Xebia. With more than ten years of experience in IT he has held many roles, ranging from developer, software architect, team lead and Scrum master. During his career he has explored a broad range of languages, software development techniques and tools for the JVM platform. He is one of the first certified Scala trainers in Europe and current chairman of the Dutch Scala Enthusiast community (DUSE).
 

Sander van den Berg has been active in IT since 1999, working as a software developer for several defence related companies, mainly working on MDA/MDD solutions. He joined Xebia as a Senior Consultant in 2010, where he is responsible for Xebia’s Lean Architecture approach and the promotion of Scala. Besides architecture, Sander is interested in language design. He is active in several functional programming communities. Sander likes elegant solutions to hard problems, preferring Scala as a means to express them. Sander enjoys many languages, amongst them Clojure, Haskell and F#.

Rate this Article

Adoption
Style

BT