Java/Scala (deep) collections interoperability
Could you please share your opinion on what is the most elegant and/or efficient way of converting a
java.util.Ha开发者_StackOverflow社区shMap[
java.lang.String, java.util.ArrayList[
java.util.ArrayList[java.lang.Double]
]
]
(all of the objects are from java.util or java.lang)
to
Map[
String, Array[
Array[Double]
]
]
(all of the objects are from scala)
Thanks, -A
I'm not claiming that it's all that elegant, but it works. I use the conversions from JavaConversions
explicitly rather than implicitly to allow type inference to help a little. JavaConversions
is new in Scala 2.8.
import collection.JavaConversions._
import java.util.{ArrayList, HashMap}
import collection.mutable.Buffer
val javaMutable = new HashMap[String, ArrayList[ArrayList[Double]]]
val scalaMutable: collection.Map[String, Buffer[Buffer[Double]]] =
asMap(javaMutable).mapValues(asBuffer(_).map(asBuffer(_)))
val scalaImmutable: Map[String, List[List[Double]]] =
Map(asMap(javaMutable).mapValues(asBuffer(_).map(asBuffer(_).toList).toList).toSeq: _*)
UPDATE: Here is an alternative approach that uses implicits to apply a given set of conversions to an arbitrarily nested structure.
trait ==>>[A, B] extends (A => B) {
def apply(a: A): B
}
object ==>> {
def convert[A, B](a: A)(implicit a2b: A ==>> B): B = a
// the default identity conversion
implicit def Identity_==>>[A] = new (A ==>> A) {
def apply(a: A) = a
}
// import whichever conversions you like from here:
object Conversions {
import java.util.{ArrayList, HashMap}
import collection.mutable.Buffer
import collection.JavaConversions._
implicit def ArrayListToBuffer[T, U](implicit t2u: T ==>> U) = new (ArrayList[T] ==>> Buffer[U]) {
def apply(a: ArrayList[T]) = asBuffer(a).map(t2u)
}
implicit def HashMapToMap[K, V, VV](implicit v2vv: V ==>> VV) = new (HashMap[K, V] ==>> collection.Map[K, VV]) {
def apply(a: java.util.HashMap[K, V]) = asMap(a).mapValues(v2vv)
}
}
}
object test {
def main(args: Array[String]) {
import java.util.{ArrayList, HashMap}
import collection.mutable.Buffer
// some java collections with different nesting
val javaMutable1 = new HashMap[String, ArrayList[ArrayList[Double]]]
val javaMutable2 = new HashMap[String, ArrayList[HashMap[String, ArrayList[ArrayList[Double]]]]]
import ==>>.{convert, Conversions}
// here comes the elegant part!
import Conversions.{HashMapToMap, ArrayListToBuffer}
val scala1 = convert(javaMutable1)
val scala2 = convert(javaMutable2)
// check the types to show that the conversion worked.
scala1: collection.Map[String, Buffer[Buffer[Double]]]
scala2: collection.Map[String, Buffer[collection.Map[String, Buffer[Buffer[Double]]]]]
}
}
The method for doing this has changed from 2.7 to 2.8. Retronym's method works well for 2.8. For 2.7, you'd instead use collections.jcl
like so:
object Example {
import scala.collection.jcl
// Build the example data structure
val row1 = new java.util.ArrayList[Double]()
val row2 = new java.util.ArrayList[Double]()
val mat = new java.util.ArrayList[java.util.ArrayList[Double]]()
row1.add(1.0) ; row1.add(2.0) ; row2.add(3.0) ; row2.add(4.0)
mat.add(row1) ; mat.add(row2)
val named = new java.util.HashMap[String,java.util.ArrayList[java.util.ArrayList[Double]]]
named.put("matrix",mat)
// This actually does the conversion
def asScala(thing: java.util.HashMap[String,java.util.ArrayList[java.util.ArrayList[Double]]]) = {
Map() ++ (new jcl.HashMap(thing)).map(kv => {
( kv._1 ,
(new jcl.ArrayList(kv._2)).map(al => {
(new jcl.ArrayList(al)).toArray
}).toArray
)
})
}
}
So, the general idea is this: from the outside in, wrap the Java collection in a Scala equivalent, then use map to wrap everything in the next level. If you want to convert between Scala representations, do that on the way out (here, the .toArray
at the ends).
And here you can see the example working:
scala> Example.named
res0: java.util.HashMap[String,java.util.ArrayList[java.util.ArrayList[Double]]] = {matrix=[[1.0, 2.0], [3.0, 4.0]]}
scala> val sc = Example.asScala(Example.named)
sc: scala.collection.immutable.Map[String,Array[Array[Double]]] = Map(matrix -> Array([D@1ea817f, [D@dbd794))
scala> sc("matrix")(0)
res1: Array[Double] = Array(1.0, 2.0)
scala> sc("matrix")(1)
res2: Array[Double] = Array(3.0, 4.0)
@retronym's answer is good if your collections are homogenous, but it didn't seem to work for me when I had a mixed collection. For example:
val x = Map(5 -> Array(1, List(2, 7), 3), 6 -> Map(5 -> List(7, 8, 9)))
To convert such a collection to java, you need to rely on runtime types because the type of x
is not something that can be recursively converted to java at compile time. Here is a method that can handle converting mixed scala collections to java:
def convert(x:Any):Any =
{
import collection.JavaConversions._
import collection.JavaConverters._
x match
{
case x:List[_] => x.map{convert}.asJava
case x:collection.mutable.ConcurrentMap[_, _] => x.mapValues(convert).asJava
case x:collection.mutable.Map[_, _] => x.mapValues(convert).asJava
case x:collection.immutable.Map[_, _] => x.mapValues(convert).asJava
case x:collection.Map[_, _] => x.mapValues(convert).asJava
case x:collection.mutable.Set[_] => x.map(convert).asJava
case x:collection.mutable.Buffer[_] => x.map(convert).asJava
case x:Iterable[_] => x.map(convert).asJava
case x:Iterator[_] => x.map(convert).asJava
case x:Array[_] => x.map(convert).toArray
case _ => x
}
}
A similar method can be written for converting from java to scala.
Note that the return type of this method is Any
, so in order to use the value returned, you may have to perform a cast: val y = convert(x).asInstanceOf[java.util.Map[Int, Any]]
.
精彩评论