Mike Slinn

scala.jdk Converter Libraries Part 1

— Draft —

Published 2014-03-10. Last modified 2019-07-20.
Time to read: 4 minutes.

Scala 2.13 introduced many new converters, most of which are detailed in this and the following lecture.

The code for this lecture is provided in collections.ConverterFun.

Converter History

Back in the mists of time JavaConversions was the only way to convert between Scala and Java collections. JavaConversions assumed that every collection needs to be wrapped and did not allow for control of what is converted. The unnecessary conversions added unnecessary overhead, so JavaConversions was quickly replaced by the scala.collection.JavaConverters object. JavaConverters did a better job, however JavaConversions was not removed for many releases. Scala 2.12 deprecated JavaConversions. For Scala 2.13, the JavaConverters object was moved to the scala.jdk package and renamed to CollectionConverters.

Prior to Scala 2.13, other converters were provided in the optional scala-java8-compat library: DurationConverters, FunctionConverters, OptionConverters and StreamConverters. These converters were folded into the Scala 2.13 standard library. All of the scala-java8-compat methods were placed in the scala.jdk package; before they had been scattered across a variety of packages (scala.collection, scala.concurrent, etc.).

The scala.jdk Package

The scala.jdk package contains the javaapi package which defines various conversions between Scala and Java types.

Object Converts Between
CollectionConverters Scala / Java collections
DurationConverters Scala and Java duration types
FunctionConverters Scala and Java function types
FutureConverters Scala Future and Java CompletionStage
OptionConverters Scala Option and Java Optional types.
StreamConverters Creates Java Streams that operate on Scala collections (sequentially or in parallel)

You bring these into scope with an import statement, such as.

import scala.jdk.CollectionConverters._
import scala.jdk.DurationConverters._
import scala.jdk.FunctionConverters._
import scala.jdk.FutureConverters._
import scala.jdk.OptionConverters._
import scala.jdk.StreamConverters._

We'll look at almost all of these converters in this lecture. Most programmers just want collection conversions.

import scala.jdk.CollectionConverters._

Beware! Importing CollectionConverters and FunctionConverters causes a name collision. I will show you how to deal with that in the FunctionConverters section later in this lecture.

Converting to and from Java Collections

scala.jdk.CollectionConverters provides converters for commonly-used Java collections with asScala methods and Scala collections with asJava methods. Throughout the collection classes (including Option), methods are named asScala and asJava for wrappers, while methods named toJava and toScala indicate conversions that copy data to new objects.

Bidirectional Collection Converters

CollectionConverters provides the following two-way conversions. If you do a round-trip conversion using these bidirectional converters you end up with the original collection. These bidirectional converters do not make copies of the data, instead they merely wrap it differently. Remember that a Scala Buffer is just a mutable List.

scala.collection.Iterator[T]                🡄🡆   java.util.{ Iterator[T], Enumeration[T] }
scala.collection.mutable.Buffer[T]          🡄🡆   java.util.List[T]
scala.collection.mutable.Set[T]             🡄🡆   java.util.Set[T]
scala.collection.mutable.Map[K,V]           🡄🡆   java.util.{ Map[K,V], Dictionary[K,V] }
scala.collection.mutable.ConcurrentMap[T]   🡄🡆   java.util.concurrent.ConcurrentMap[T]

Note that Java's Enumeration is not used by most software any more, and it has been replaced by Iterator. Similarly, Java's Dictionary has long been deprecated; Map has been in use in its place for many years.

 For example, we can convert from collection.mutable.ListBuffer to java.util.List and then back to collection.mutable.ListBuffer.

Scala REPL
scala> scala> import scala.collection._
import collection._
scala> scala> import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters._
scala> scala> val list1 = mutable.ListBuffer(1, 2, 3) sl: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3)
scala> scala> val jl = list1.asJava jl: java.util.List[Int] = [1, 2, 3]

As you know, it is best to define your types, and so this is how you could do that for j1.

Scala REPL
scala> scala> val jl: java.util.List[Int] = list1.asJava   // the type of j1 need not be declared, merely added for clarity
jl: java.util.List[Int] = [1, 2, 3]

If the program you are working on uses a lot of Java Lists and Scala Lists, you'll find it convenient to define an alias for the Java List so you can tell them apart without typing out the fully qualified type. For example this is how to define an alias called JList for a Java List.

Scala REPL
scala> scala> import java.util.{List => JList}
import java.util.{List=>JList}

Now we can declare j1's type in a more convenient manner.

Scala REPL
scala> scala> val jl: JList[Int] = list1.asJava   // the type of j1 need not be declared, merely added for clarity
jl: java.util.List[Int] = [1, 2, 3]

Note that the alias is not used by the Scala REPL when echoing back the results of a computatio.

Now let's convert the Java List back to a Scala List.

Scala REPL
scala> scala> val s2: mutable.Buffer[Int] = jl.asScala  // the type of s2 need not be declared, merely added for clarity
s2: scala.collection.mutable.Buffer[Int] = ListBuffer(1, 2, 3)
scala> scala> assert(list1 eq s2)

We discussed assert and require in the Predef lecture earlier in this course.

Here is another example.

Scala REPL
scala> scala> val i1 = Iterator(1, 2, 3)
i1: Iterator[Int] = non-empty iterator
scala> scala> val i2 = i1.asJava.asScala i2: Iterator[Int] = non-empty iterator
scala> scala> assert(i1 eq i2)

And another example.

Scala REPL
scala> scala> val m1 = mutable.HashMap(1 -> "eh", 2 -> "bee", 3 -> "sea")
m1: scala.collection.mutable.HashMap[Int,String] = Map(2 -> bee, 1 -> eh, 3 -> sea)
scala> scala> val m2 = m1.asJava.asScala m2: scala.collection.mutable.Map[Int,String] = Map(2 -> bee, 1 -> eh, 3 -> sea)
scala> scala> assert(m1 eq m2)

Unidirectional Converters

CollectionConverters provides the following one-way conversions. Keep in mind that the default implementation of immutable.Seq is immutable.List, so any time you ask for a new Seq you actually get a new List.

scala.collection.immutable.Seq[T]   🡆   java.util.List[T]
scala.collection.mutable.Seq[T]     🡆   java.util.List[T]
scala.collection.immutable.Range    🡆   java.util.List[Int]
scala.collection.immutable.Set[T]   🡆   java.util.Set[T]
scala.collection.immutable.Map[T]   🡆   java.util.Map[T]

Recall that java.util.List is an interface, and that Java List implementations are mutable. Contrast this with Scala's List, which is an implementation of the immutable.Seq trait. This means that Scala's mutable.Buffer trait, which is a mutable List, is the best approximation of Java's List. For convenience, let's define JList as an alias for java.util.List; that way we'll know that List refers to Scala's List.

Scala REPL
scala> scala> import java.util.{List => JList}
import java.util.{List=>JList}
scala> scala> val list2: Seq[Int] = List(1, 2, 3) list: Seq[Int] = List(1, 2, 3)
scala> scala> val j2: JList[Int] = list2.asJava // the type of j2 need not be declared, merely added for clarity j2: java.util.List[Int] = [1, 2, 3]
scala> scala> val s2: mutable.Buffer[Int] = list2.asJava.asScala // the type of s2 need not be declared, merely added for clarity s2: scala.collection.mutable.Buffer[Int] = Buffer(1, 2, 3)
scala> scala> assert(list2 ne s2)

Scala's mutable and immutable Seqs are actually implemented as mutable.Buffer and immutable.List. Converting either of these to Java with the asJava method yields the same type, java.util.List.

Scala REPL
scala> scala> immutable.Seq(1,2,3).asJava
res4: java.util.List[Int] = [1, 2, 3]
scala> scala> mutable.Seq(1,2,3).asJava res5: java.util.List[Int] = [1, 2, 3]

Attempting round-trip them from Scala, to Java and back to Scala does not restore the original collection; instead, they are both round-tripped to mutable.Buffer.

Scala REPL
scala> scala> mutable.Seq(1,2,3).asJava.asScala
res2: scala.collection.mutable.Buffer[Int] = Buffer(1, 2, 3)
scala> scala> immutable.Seq(1,2,3).asJava.asScala res3: scala.collection.mutable.Buffer[Int] = Buffer(1, 2, 3)

We can also convert Scala and Java Sets. Remember that Scala's Sets are traits (mutable.Set and immutable.Set), and Java's Set is an interface. The Scala Set traits and the Java Set interface each have multiple implementations. For convenience, let's define JSet as an alias for java.util.Set.

Scala REPL
scala> scala> import java.util.{ Set => JSet }
import java.util.{Set=>JSet}
scala> scala> val set = immutable.HashSet(1, 2, 3) set: scala.collection.immutable.HashSet[Int] = Set(1, 2, 3)
scala> scala> val jSet: JSet[Int] = set.asJava // the type of jSet need not be declared, merely added for clarity jSet: java.util.Set[Int] = [1, 2, 3]
scala> scala> val scala> sSet: mutable.Set[Int] = jSet.asScala // the type of sSet need not be declared, merely added for clarity sSet: scala.collection.mutable.Set[Int] = Set(1, 2, 3)
scala> scala> assert(set ne sSet)

We can also convert Scala and Java Maps. Remember that Scala's Maps are traits (mutable.Map and immutable.Map), and Java's Map is an interface. The Scala Map traits and the Java Map interface each have multiple implementations.

Scala REPL
scala> scala> import java.util.{ Map => JMap }
import java.util.{Map=>JMap}
scala> scala> val map = immutable.HashMap(2 -> "bee", 1 -> "eh", 3 -> "sea") map: scala.collection.immutable.HashMap[Int,String] = Map(1 -> eh, 2 -> bee, 3 -> sea)
scala>scala> val jMap: JMap[Int, String] = map.asJava // the type of jMap need not be declared, merely added for clarity jMap: java.util.Map[Int,String] = {1=eh, 2=bee, 3=sea}
scala> scala> val sMap: mutable.Map[Int, String] = jMap.asScala // the type of sMap need not be declared, merely added for clarity sMap: scala.collection.mutable.Map[Int,String] = Map(1 -> eh, 2 -> bee, 3 -> sea)
scala> scala> assert(map ne sMap)

We can do the same with sorted maps.

Scala REPL
scala> scala> val treeMap = immutable.TreeMap(2 -> "bee", 1 -> "eh", 3 -> "sea")
treeMap: scala.collection.immutable.TreeMap[Int,String] = Map(1 -> eh, 2 -> bee, 3 -> sea)
scala> scala> val jMap2: JMap[Int, String] = treeMap.asJava jMap2: java.util.Map[Int,String] = {1=eh, 2=bee, 3=sea}
scala> scala> val sMap2: mutable.Map[Int, String] = jMap2.asScala sMap2: scala.collection.mutable.Map[Int,String] = Map(1 -> eh, 2 -> bee, 3 -> sea)
scala> scala> assert(treeMap ne sMap2)

Exercise

System.getenv returns a java.util.Map[String, String]. Write a Scala console program that accepts a String to do a case-insensitive search of the values contained in the Map. The method should convert the Java Map to a scala.collection.mutable.Map[String,String] and then use a filter to obtain a collection of all the values that contain the given word. Convert the collection to a List and display it.

Because IDEA does not pass environment variables to worksheets, and does not provide any mechanism to define environment variables the way it does for run/debug configurations, do not attempt to use a Scala worksheet for this exercise. Also, please use Scala 2.13 syntax; however as of July 19, 2019 there was no released version of Ammonite that embeds that version of Scala.

Hint: you can use the placeholder syntax in a higher-order function in order to examine the values of a Map: _._2. We discussed placeholder syntax in the More Fun With Functions lecture of the Introduction to Scala course.

Although sys.env returns a Scala Map, and would normally be recommended for production code, this exercise is intended to give you practice on converting a Java collection to a Scala collection, so use of sys.env is considered cheating for this exercise.

Solution

package solutions
import scala.jdk.CollectionConverters._
object EnvSearch extends App { def findEnvValuesWith(name: String): List[String] = System.getenv .asScala .filter( _._2.toLowerCase contains name.toLowerCase ) .values .toList
if (args.isEmpty) println("Please provide a text string to search environment values for.") else println(findEnvValuesWith(args.head).mkString("\n")) }

This code is provided in solutions/EnvSearch.scala. Run it by typing.

Scala REPL
$ scala> sbt "runMain solutions.EnvSearch wordToSearchFor"

OptionConverters

I believe that OptionConverters should have been included in CollectionConverters, but the Scala developers disagree with me. OptionConverters provides the following two unidirectional conversions. You can round-trip between scala.Option and java.util.Optional, and the results of the round trip will be values and types which are the same as those you started with, however each time one of these conversions is invoked, a new class instance is created and the value is copied into the result.

scala.Option[T]   🡄   java.util.Optional[T]
scala.Option[T]   🡆   java.util.Optional[T]

You need to add this import to any code that needs to convert between Scala's Option and Java's Optional.

Scala REPL
scala> scala> import scala.jdk.OptionConverters._
import scala.jdk.OptionConverters._

We need a Java Optional instance to play with.

Scala REPL
scala> scala> import java.util.Optional
import java.util.Optional
scala> scala> val x: Optional[String] = Optional.of("hi") x: java.util.Optional[String] = Optional[hi]

Now let's try out the converters.

Scala REPL
scala> scala> x.asScala
         ^
       warning: method asScala in class RichOptional is deprecated (since 2.13.0): Use `toScala` instead
res0: Option[String] = Some(hi)

As I mentioned earlier, methods are named asScala and asJava for wrappers, while methods named toJava and toScala indicate conversions that copy data to new objects. Since we must use toScala, we know that a new class instance is created for each conversion between Scala Option and Java Optional.

Scala REPL
scala> scala> val scalaOption = x.toScala
scalaOption: Option[String] = Some(hi)
scala> scala> scalaOption.toJava res2: java.util.Optional[String] = Optional[hi]

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.