1. Overview

Scala is part of the Java Virtual Machine ecosystem. Therefore, any non-green field project will probably interact with Java libraries. Besides, those interactions might require us to either pass Java collections as parameters, receive them as return values, or most likely both.

We could use Java Collections in our Scala code, but this would mean abandoning Scala features we love. For example, Scala’s functional interfaces, like map and foreach. And above all, we’ll lose access to Scala’s monadic for. Therefore, we’ll like to operate on Java collections in the same way we do on Scala ones.

In this tutorial, we’ll learn how to convert Java to Scala collections and vice-versa; furthermore, we’ll learn how to use idiomatic Scala to perform those transformations.

We’ll achieve this by leveraging the conversion methods provided by the Standard Scala library. Also, we’ll explore the different options to bring those conversions into scope.

2. A Short History of the Collection Conversions

Scala has strived to be compatible with Java since the beginning and included wrappers to transform from Java to Scala and Scala to Java.

We’ll avoid these wrappers and their companion object since they are deprecated.

2.1. Overview of the Current Implementation

The current implementation of conversions uses implicit conversions.

We can mix-in DecorateAsScala and DecorateAsJava traits. Therefore, limiting the amount and scope of the implicit conversions:

class JavaToScalaConversionsTest extends FlatSpec with Matchers with DecorateAsJava {
  // ...
}

Or import the JavaConverters object, we can control the availability of the conversions using local imports; however, this means importing all the implicit conversions:

class CollectionConversionsTest extends FlatSpec with Matchers { 

  import scala.collection.JavaConverters._

  // ...
}

We can perform the most common conversions using the asJava or asJava polymorphic extension methods. In other words, we can achieve the following transformations:

 

Scala Java
scala.collection.Iterable java.lang.Iterable
scala.collection.Iterator java.util.Iterator
scala.collection.mutable.Buffer java.util.List
scala.collection.mutable.Set java.util.Set
scala.collection.mutable.Map java.util.Map
scala.collection.concurrent.Map java.util.concurrent.ConcurrentMap

 

There are other conversions, some of the unidirectional. The complete list is available in the official Scala documentation.

The implementation is efficient and avoids copying the collections by using the decorator pattern. Even more, double decorators cancel each other. Therefore if we call asScala after calling asJava, or vice versa, we’ll receive the original object instance:

class CollectionConversionsTest extends FlatSpec with Matchers {

  import scala.collection.JavaConverters._

  "Round trip conversions from Java to Scala" should "not have any overhead" in {
    val javaList = new ArrayList[Int]
    javaList.add(1)
    javaList.add(2)
    javaList.add(3)
    assert(javaList eq javaList.asScala.asJava)
  }

  "Round trip conversions from Scala to Java" should "not have any overhead" in {
    val scalaSeq = Seq(1, 2, 3).toIterator
    assert(scalaSeq eq scalaSeq.asJava.asScala)
  }
}

In mutable collections, the side effects will be visible in both languages.

However, conversions from scala immutable collections that result in mutable interfaces will throw an UnsupportedOperationException:

  "Conversions to mutable collections" should "throw an unsupported operation exception" in {
    val scalaSeq = Seq(1, 2, 3)
    val javaList = scalaSeq.asJava
    assertThrows[UnsupportedOperationException](javaList.add(4))
  }

3. Let’s Practice Some Conversions

3.1. From Java to Scala Functional Code

The most interesting case is converting from Java to Scala. When interacting with Java libraries, we might receive a Java Collection as a result. Which we’ll pass back to the same or another Java library after manipulating it in our code. This is an ideal scenario for the standard bidirectional conversions to shine.

Let’s review some examples.

We receive a Java Iterator<Integer> which we’d like to increment it and pass it back to another function:

class JavaToScalaConversionsTest extends FlatSpec with Matchers
  with DecorateAsScala with DecorateAsJava {

  "Standard conversions" should "convert from Java Iterators and back" in {
    val api = new JavaApi
    val javaList = api.getOneToFive
    val incremented = javaList.asScala.map(_ + 1).map(Integer.valueOf(_))

    assert(api.iteratorToString(incremented.asJava) == "[2, 3, 4, 5, 6]")
  }
}

The example above showcases how we need to be extra careful while working with collections of Java’s primitive type wrappers. For instance, after incrementing the collection, the type in Scala will be Iterator[Int], and if we call asJava on it, the result will be java.util.Iterator[Int].

In conclusion, Scala collection conversions transform the collection’s structure but do not touch the contents.

3.2. From Java to Scala Imperative Code

We receive a Java mutable List which we’ll like to process and pass back to another function:

  "Standard conversions" should "support Java's lists" in {
    val api = new JavaApi
    val javaList = api.getNames

    val scalaList = for (name <- javaList.asScala) yield s"Hello ${name}"
    val withExclamation = api.addExclamation(scalaList.asJava)

    assert(withExclamation.toString == "[Hello Oscar!, Hello Helga!, Hello Faust!]")
    assert(!(withExclamation eq javaList))
  }

In the code above, we interacted with Java code using the collections it returned in idiomatic Scala code. However, as the last assertion demonstrates, we ended up creating a new collection. This is normal since functional programming favors immutability, and the compiler will minimize most of the negative impact in performance from creating many objects.

Nevertheless, we might encounter situations where creating new instances is unacceptable; on such occasions, we can still convert the Java collection to Scala and avoid using functional programming constructs and write imperative code:

  "Standard conversions" should "support Java's mutable lists" in {
    val api = new JavaApi
    val javaList = api.getNames

    val scalaList = javaList.asScala
    for (ix <- 0 until scalaList.size) {
      scalaList(ix) = s"Hi ${scalaList(ix)}"
    }
    val withExclamation = api.addExclamation(scalaList.asJava)

    assert(withExclamation.toString == "[Hi Oscar!, Hi Helga!, Hi Faust!]")
    assert(withExclamation eq javaList)
  }

Above, We were able to write code that manipulated a mutable collection in both languages and didn’t create new collection instances. However, code clarity and correctness are usually more important than raw performance. We should only resort to this imperative style for the most performance-sensitive parts of our code.

Other conversions from Java to Scala work similarly, with one notable exception.

3.3. A Special Case of Java to Scala Conversion

Sometimes we might have to deal with Java Properties in our Scala code. We can convert Java Properties to a Scala Map. But keep in mind this will be a one-way conversion since there is no standard conversion that allows us to turn a Scala Map into a Java Properties object:

  "Java properties" should "be converted to a Scala Map" in {
    val api = new JavaApi
    val javaProps = api.getConfig

    assert(javaProps.asScala == Map("name" -> "Oscar", "level" -> "hard"))
  }

3.4. From Scala to Java

Since most conversions are bi-directional, We’ve already used the asScala extension method when exploring how to transform Java into Scala collections.

Sometimes, the API requires a particular type; for these cases, the standard library provides us with three specialized methods: asJavaCollection, asJavaEnumeration, and asJavaDictionary. They are alternatives available for Scala’s Iterable, Iterator, and concurrent Map, respectively:

class ScalaToJavaConversionsTest extends FlatSpec with Matchers
  with DecorateAsJava {

  "A Scala Iterable" should "be passable as a parameter expecting a Java Collection" in {
    val api = new JavaApi

    val scalaIterable = Seq(1, 2, 3).toIterable
    assert(api.collectionSize(scalaIterable.asJavaCollection) == "Collection of size: 3")
  }
}

On top of those conversions, the asJava extension method enables unidirectional transformations from Scala’s Seq and mutable Seq to Java List, Scala to Java Sets, and Scala’s immutable Map to Java Map.

4. Avoiding Implicit Conversions

Some organizations view implicit conversions with suspicion, and their use is discouraged.

Fortunately, we can write equivalent code explicitly calling the conversion methods; they are all prefixed by an as, making them easily discoverable when using an IDE.

5. Conclusion

In this tutorial, we explored how the standard conversions between Scala and Java collections facilitate interoperability with Java libraries.

We also understood when the implementation minimizes overhead, and above all, how and when to use imperative style to avoid creating extra collection instances.

As always, the full source code of the article is available over on GitHub.
guest
0 Comments
Inline Feedbacks
View all comments