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.
In Scala 2.12, 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._
// ...
}
However, these methods are removed from Scala versions 2.13 and above. Now, it is suggested to use the implicit conversions from the package scala.jdk by using the import statement:
import scala.jdk.CollectionConverters._
We can perform the most common conversions using the asJava or asScala 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 {
"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
class JavaToScalaConversionsTest extends FlatSpec with Matchers {
import scala.jdk.CollectionConverters._
"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.
The standard library provides us the method asJavaCollection to perform the conversion:
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)
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. Moreover, we also saw that there are some changes between Scala 2.12 and 2.13 regarding conversions.