Going to and from Java Maps in Scala (JavaConversions)

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 11.25, “Going To and From Java Maps in Scala”

Problem

You need to share a map with a Java method, or access a Java map in Scala code.

Solution

To use a Java map in Scala code, import the mapAsScalaMap method from the JavaConversions package, and perform the conversion.

Simply trying to use a Java HashMap with something like a Scala for loop won’t work. To demonstrate this, first create a simple Java HashMap:

val jmap = new java.util.HashMap[String, String]()
jmap.put("fname", "Alvin")
jmap.put("lname", "Alexander")

Next, attempt to use the HashMap in a Scala for loop. You’ll see that it won’t work:

scala> for ((k,v) <- jmap) println(s"key: $k, value: $v")
<console>:9: error: value filter is not a member of java.util.HashMap[String, String]
              for ((k,v) <- jmap) println(s"key: $k, value: $v")
                            ^

However, by simply importing the correct method from the JavaConversions package, the for loop magically works:

scala> import scala.collection.JavaConversions.mapAsScalaMap
import scala.collection.JavaConversions._

scala> for ((k,v) <- jmap) printf("key: %s, value: %s\n", k, v)
key: lname, value: Alexander
key: fname, value: Alvin

Discussion

As demonstrated in Recipe 17.1, “Going to and from Java Collections”, the JavaConversions package makes the process of going back and forth between Java collections very simple. The key to performing conversions is knowing which collections map to each other. There are lookup tables in that recipe that will help you make that choice.

Some developers don’t like the “magic” of implicit conversions, so if you prefer to be explicit about the conversion, call the conversion method from the JavaConversions package directly (after importing it):

scala> val smap = mapAsScalaMap(jmap)
smap: scala.collection.mutable.Map[String,String] = Map(lname -> Alexander, fname -> Alvin)

Java maps that don’t use generics

You can run into a problem when working with Java maps that don’t use generics. For instance, I ran into a problem when extending a Java interface as a Scala class. The BasicPlayerListener is a Java interface whose opened method is declared like this:

// java
void opened(java.lang.Object stream, java.util.Map properties);

The beginning of my Scala class definition looks like this:

class MyListener extends BasicPlayerListener {
    def opened(stream: Any, properties:Map[_, _] ) { // code here ...

Because I was implementing this interface, I needed to declare the Map in my Scala opened method as follows to work with the java.util.Map reference in the opened method of the Java interface:

def opened(stream: Any, properties:Map[_, _] ) {
    println("opened() called")
}

Notice that it was necessary to define the map as Map[_, _]. Fortunately Eclipse and the ScalaIDE project provided help when I was working on this override, because I first attempted to define the map as Map[Any, Any], and got this error message:

Note that java.util.Map does not match java.util.Map[Any,Any].
To implement a raw type, use java.util.Map[_, _].

In addition to defining the Map with that syntax, I also needed these two import statements in my Scala code to make everything work:

import scala.collection.JavaConversions._
import java.util.Map

See Also

  • Recipe 17.1, “Going to and from Java Collections” for details on the JavaConversions object
  • The JavaConversions object