Thursday, February 25, 2010

How to Convert a Java List to a Scala List (actually... let's not do that)

If you're using Scala and still making use of pretty much any Java library, you'll pretty soon come up against this problem: you're Java library has returned you a java.util.List, but you want to use the awesome methods contained in Scala's scala.List (soon to be scala.collection.immutable.List).

I'll show you how to do what you want in a second, but first, and importantly, I need to explain that what you actually want to do is NOT convert a Java List to a Scala List. Why? It's because, despite the name similarity, they're actually very different things...

The difference is that Java's List is an interface, while Scala's List is an implementation. The List interface in Java represents a concept: a collection of elements that maintains insertion order (i.e. a sequence) and allows, among other things, random access to its elements. The most commonly used implementation is ArrayList, which is mutable (i.e. invoking add(element) changes the existing list) and has constant-time random access. Unlike Java's List interface, Scala's List is a specific implementation of a sequence. In that sense, it is a parallel to ArrayList, but in many other senses, it's nothing like it. The List class imported by default in Scala is an immutable linked list - it can't be changed after it's been created and operations requiring random access take linear time. Scala's List is not a direct substitute for Java's List and it's extremely different to ArrayList as well. So, what to do?

Most people want to convert their Java List to a Scala List just so they can use cool Scala functions like map(), filter(), find(), foreach(), foldLeft() and flatMap(). And that's a perfectly good excuse. But if you look into the Scala API, you'll see that all of these useful methods are available on any class that implements the Iterable trait. (From Scala 2.8, they are also available on the Traversable trait, which the Iterable trait extends.)

If we look a bit further into the Scala API, we'll see that there's also a trait called Seq, which is a slightly better parallel to Java's List than the Iterable or Traversable traits. So what you really want to end up with when you bring your Java List into your Scala code is a Scala Seq, not a Scala List.

That's enough jibber-jabber. I'm sure you've learnt your lesson.

Here's a great way to convert a Java List to a Scala Seq in Scala 2.7:

scala> var javaList = new java.util.ArrayList[Int]()
javaList: java.util.ArrayList[Int] = []

scala> javaList.add(3)
res0: Boolean = true

scala> javaList.add(4)
res1: Boolean = true

scala> javaList.add(5)
res2: Boolean = true

scala> val scalaSeq = new collection.jcl.BufferWrapper[Int]() {
def underlying = javaList
}
scalaSeq: java.lang.Object with scala.collection.jcl.BufferWrapper[Int]{def underlying: java.util.ArrayList[Int]} = [3, 4, 5]

scala> println(javaList)
[3, 4, 5]

scala> println(scalaSeq)
[3, 4, 5]

In Scala 2.8, the EPFL LAMP guys have realised the importance of this kind of conversion and gone one better by providing a class full of conversions that do exactly what we want, like this:


scala> var javaList = new java.util.ArrayList[Int]()
javaList: java.util.ArrayList[Int] = []

scala> javaList.add(3)
res0: Boolean = true

scala> javaList.add(4)
res1: Boolean = true

scala> javaList.add(5)
res2: Boolean = true

scala> val scalaSeq = scala.collection.JavaConversions.asBuffer(javaList)
scalaSeq: scala.collection.mutable.Buffer[Int] = Buffer(3, 4, 5)

scala> println(javaList)
[3, 4, 5]

scala> println(scalaSeq)
Buffer(3, 4, 5)

Of course, so much of the appeal of Scala is attributable to having to write less code, so it would be nice to not even have to invoke these operations within functions but instead to have implicit functions that will perform the conversions whenever they're necessary.

In Scala 2.7, the implicit conversion can be defined like this:

implicit def javaList2Seq[T](javaList: java.util.List[T]) : Seq[T] =
new BufferWrapper[T]() { def underlying = javaList }

And in Scala 2.8, all you need to do is import the existing implicit functions!

import scala.collection.JavaConversions._