How can I convert a Java map of maps for use in Sc

2019-06-22 11:54发布

问题:

I working on a Scala program that calls a function from a Java library, processes the results, and spits out a CSV.

The Java function in question looks like this:

    Map<String, Map<String, AtomicLong>> getData();

The Scala:

    import scala.collection.JavaConversions._
    def analysisAndCsvStuff(data: Map[String, Map[String, AtomicLong]]): Unit { ... }

The error:

    type mismatch;
    found:java.util.Map[java...String,java...Map[java...String,java...AtomicLong]]
    required: scala...Map[String,scala...Map[String,java...AtomicLong]]

(The path names were ruining the formatting.)

I'm guessing that the JavaConversions can successfully convert the outer java...Map but not the inner java...Map. I saw this question but I am unsure of how to go about writing an "explicit implicit conversion".

回答1:

Edit: the recommended way is to use JavaConverters and the .asScala method:

import scala.collection.JavaConverters._
val myScalaMap = myJavaMap.asScala.mapValues(_.asScala)

Note that you will get mutable maps out of this. You can always use .asScala.toMap if you want immutable ones.


The original answer with JavaConversions:

The short answer is: call .mapValues on the outer map to convert the inner map:

import scala.collection.JavaConversions._
val myScalaMap = myJavaMap.mapValues(_.toMap)

.mapValues forces the conversion or the outer map to a scala Map and .toMap forces the conversion of the inner map to a scala (immutable) map. The immutable part isn't strictly necessary, but anyways...

This is very similar to this anwser. Short example:

scala> val a: java.util.Map[String, java.util.Map[String, String]] = new java.util.HashMap[String, java.util.Map[String, String]]
a: java.util.Map[String,java.util.Map[String,String]] = {}


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

scala> val myScalaMap = a.mapValues(_.toMap)
myScalaMap: scala.collection.Map[String,scala.collection.immutable.Map[String,String]] = Map()


回答2:

I am unsure of how to go about writing an "explicit implicit conversion"

Using JavaConverters of course, that phrase suggests a custom implicit.

Here is the back and forth:

scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._

scala> import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicLong

scala> val m = Map("a" -> Map("b" -> new AtomicLong(7L)))
m: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,java.util.concurrent.atomic.AtomicLong]] = Map(a -> Map(b -> 7))

scala> val j = m mapValues (_.asJava) asJava
warning: there were 1 feature warning(s); re-run with -feature for details
j: java.util.Map[String,java.util.Map[String,java.util.concurrent.atomic.AtomicLong]] = {a={b=7}}

scala> implicit class mapmap[A,B,C](val v:
     | java.util.Map[A,java.util.Map[B,C]]) extends AnyVal {
     | def asMapMap: Map[A,Map[B,C]] =
     | v.asScala.toMap.mapValues(_.asScala.toMap)
     | }
defined class mapmap

scala> j.asMapMap
res0: Map[String,Map[String,java.util.concurrent.atomic.AtomicLong]] = Map(a -> Map(b -> 7))


回答3:

First of all, there are 2 versions of Maps and other major collections in scala, mutable and immutable. The default of use is immutable version, and you can access mutable version through scala.collection.mutable package.

Because of the mutability of java Collections, converting java Collections to scala mutable Collections is pretty much easier than converting to scala immutable Collections. JavaConversions is unable to convert java Collections to scala immutable Collections.

Furthermore, JavaCollections cannot convert your inner collections to scala types.

I suggest you to convert your java Map as a scala data structure as follows.

val javaMap:java.util.HashMap[String,java.util.Map[String,Int]] = new java.util.HashMap()

val scalaMap:Map[String,Map[String,Int]] = javaMap.toMap.map { case (k,v) => k -> v.toMap}

The .tomap method is defined in scala.collection.mutable.Map which converts mutable Map to immutable Map. and JavaConversions converts java.util.Map to scala immutable Map



回答4:

alternatively you can use scalaj-collection library i wrote specifically for this purpose

import com.daodecode.scalaj.collection._

analysisAndCsvStuff(getData.deepAsScala)

that's it. It will convert all nested java collections and primitive types to scala versions. You can also convert directly to immutable data structures using deepAsScalaImmutable (with some copying overhead of course)