2010-02-13 7 views
16

Pourriez-vous s'il vous plaît partager votre opinion sur ce qui est la façon la plus élégante et/ou efficace de convertir unJava/Scala (profond) collections interopérabilité

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) 

à

Map[ 
    String, Array[ 
     Array[Double] 
    ] 
] 
(all of the objects are from scala) 

Merci, - A

+0

N'est-ce pas plus un problème de conception? Quelle est la sémantique de cette structure? Pourquoi voulez-vous convertir les objets? –

+0

En fait, je suis en train de lire ces données à partir d'un fichier json à travers la bibliothèque jackson json (j'ai essayé sjson et lift-json à la fois m'a échoué). Jackson json n'a pas de scala api donc j'ai utilisé Java API pour faire le travail. –

Répondre

7

La méthode pour ce faire est passée de 2.7 à 2.8. La méthode de Retronym fonctionne bien pour 2.8. Pour 2.7, vous auriez au lieu d'utiliser collections.jcl comme ceci:

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 
    ) 
    }) 
    } 
} 

Ainsi, l'idée générale est la suivante: de l'extérieur, envelopper la collection Java dans un équivalent Scala, puis utilisez la carte pour envelopper tout dans la prochaine niveau. Si vous voulez convertir entre les représentations de Scala, faites-le à la sortie (ici, le .toArray à la fin).

Et là, vous pouvez voir l'exemple de travail:

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([[email protected], [[email protected])) 

scala> sc("matrix")(0) 
res1: Array[Double] = Array(1.0, 2.0) 

scala> sc("matrix")(1) 
res2: Array[Double] = Array(3.0, 4.0) 
13

Je ne prétends pas que c'est si élégant, mais ça marche. J'utilise explicitement les conversions de JavaConversions plutôt qu'implicitement pour permettre à l'inférence de type d'aider un peu. JavaConversions est nouveau dans 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: _*) 

MISE À JOUR: Voici une autre approche qui utilise implicits pour appliquer un ensemble de conversions à une structure arbitraire imbriquée.

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]]]]] 
    } 
} 
3

@ réponse de retronym est bonne si vos collections sont homogènes, mais il ne semble pas fonctionner pour moi quand j'avais une collection mixte. Par exemple:

val x = Map(5 -> Array(1, List(2, 7), 3), 6 -> Map(5 -> List(7, 8, 9)))

Pour convertir une telle collection java, vous devez compter sur les types d'exécution parce que le type de x n'est pas quelque chose qui peut être converti récursive java au moment de la compilation. Voici une méthode qui peut gérer la conversion des collections mixtes scala 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 
    } 
    } 

Une méthode similaire peut être écrit pour la conversion de java à scala.

Notez que le type de retour de cette méthode est Any. Par conséquent, pour utiliser la valeur renvoyée, vous devrez peut-être effectuer une conversion: val y = convert(x).asInstanceOf[java.util.Map[Int, Any]].