Java/Scala(深度)集合互操作性

17

你能否分享你的意见,关于将一个

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)

感谢, -A


这不是更多的设计问题吗?这个结构的语义是什么?你为什么想要转换这些对象? - Thomas Jung
实际上,我是通过jackson json库从json文件中读取这些数据的(我尝试了sjson和lift-json都失败了)。Jackson json没有scala api,所以我使用了java API来完成这项工作。 - Ali Salehi
3个回答

13

我并不是在说这段代码非常优雅,但它可以工作。我明确地使用了JavaConversions中的转换,而不是隐式转换,以便让类型推断能够帮助一点。 JavaConversions 是 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: _*)

更新:这里有一种替代方法,使用隐式将给定的转换应用于任意嵌套结构。

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

7

从2.7到2.8,这种方法已经有了改变。Retronym的方法在2.8上效果很好。对于2.7版本,您应该使用collections.jcl,如下所示:

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

因此,一般的想法是:从外到内,用Scala相应的方式包装Java集合,然后使用map将所有内容包装在下一个级别中。如果您想在Scala之间进行转换,请在输出时执行(这里,在末尾使用 .toArray)。

这里您可以看到示例正在工作:

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)

3
@retronym的答案非常适用于同质集合,但是对于混合集合似乎不起作用。例如:
val x = Map(5 -> Array(1, List(2, 7), 3), 6 -> Map(5 -> List(7, 8, 9)))
要将这样的集合转换为Java,您需要依赖运行时类型,因为x的类型无法在编译时递归地转换为Java。以下是一个可以处理混合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
    }   
  }

可以编写类似的方法将Java代码转换为Scala。

请注意,此方法的返回类型为Any,因此为了使用返回的值,您可能需要执行强制类型转换:val y = convert(x).asInstanceOf[java.util.Map[Int, Any]]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接