简体   繁体   中英

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.HashMap[
    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]] .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM