Scala 和 Java 的集合类型相互转换

在 Scala 和 Java 混合编程时免不了需要进行集合类型在两种语言间相互转换,更多的是在 Scala 调用 Java 的方法时把 Scala 的集合转型为 Java 的集合。典型场景是:

public void process(java.util.List<String> orderIds) {
  ......
}

上面定义的 Java 方法,如果要在 Scala 中调用它,不考虑两种语言的集体类型转换的话,可以直接传入 Java 代码要求的类型,像这样

val orderIds = new java.util.ArrayList[String]
orderIds.add("SJ001")
process(orderIds)

这样当然可以,但不能享受到 Scala 语言中集合使用的便利性,如快捷的构造,丰富的怪异的方法(++, ::, ## 等)。所以希望此时 Scala 中调用 process()  能接近这种方法

process(List("SJ001", "SJ002"))

特别是当 Java 接受一个 java.util.Map 时,能在 Scala 里直接传入 Map("key1" -> "value1",  "key2" -> "value2") 就方便许多。

用方法来完成 Scala 和 Java 间对应集合类型的转换当然没问题,但别忘了 Scala 还支持隐式转换,那就是只要在 Scala 代码中引入 collection.JavaConversions._ 对于上面的方法在 Scala 中就可以直接传入 Scala 的 List() 了。也就是

第一种办法,引入 collection.JavaConversions._ 完成双向自动转型

collection.JavaCoversions 是 Scala 2.8 开始加入的,它的定义是

而在 Trait WrapAsScala 和  WrapAsJava 中定义了很多转型的隐式方法,下面的方法完成 Scala 的 Seq 到 Java List 的转型,摘自  WrapAsJava

方法都是直接从源类型到目标类型,在 JavaCoversions 注释中,看出它能完成的转型有

* scala.collection.Iterable <=> java.lang.Iterable
* scala.collection.Iterable <=> java.util.Collection
* scala.collection.Iterator <=> java.util.{ Iterator, Enumeration }
* scala.collection.mutable.Buffer <=> java.util.List
* scala.collection.mutable.Set <=> java.util.Set
* scala.collection.mutable.Map <=> java.util.{ Map, Dictionary }
* scala.collection.concurrent.Map <=> java.util.concurrent.ConcurrentMap
* scala.collection.Seq => java.util.List
* scala.collection.mutable.Seq => java.util.List
* scala.collection.Set => java.util.Set
* scala.collection.Map => java.util.Map
* java.util.Properties => scala.collection.mutable.Map[String, String]

引入 collection.JavaCoversions._ 同样能实现 Java 集合到 Scala 集合类型的转换,如

这原本很好的完成了 Scala 代码中两种类型集合的隐式转换,但不知为何 Scala 又搞出与引入 collection.JavaConversions._ 等同效果的做法,也可以通过引入

上面三种方式的出现令人更生迷惑,实际应用起让人更不知所往。我个人建议若是要实现类型的隐式转换还是用 collection.JavaConversions._ 吧。

包 collection.convert 是这么定义,同时与前面的 JavaConversions 一对比,发现 JavaConversions 本来就是像 wrapAll 一样既实现了 WrapAsJava 也实现了 WrapAsScala。

隐式转换我们采用的是把源类型 Wrap 为目标类型。读到这里肯定也没有忽略上面除了 Wrap 的方式,还有 Decorate 方式,那就是

第二种办法,引入 collection.JavaConverters._, 进而显式调用 asJava() 或 asScala() 方法完成转型

collection.JavaCoverters._ 是 Scala 2.8.1 加进来的。如果你在应用 JavaConversions 实现类型的隐式转换经常不知所以时,或者更希望掌控来龙去脉时推荐使用 JavaCoverters 和 asJava/asScala 进行显式的类型转换。

collection.JavaCoverters 的声明如下:

是的,就是我们前面看到的 DecorateAsJava 和 DecorateAsScala, 下面方法摘自 DecorateAsJava

不像隐式转换,该方法本身不最终完成 Scala Set  到  Java List 的转换,而是隐式的获得一个中间对像 AsJava,  进一步调用  asJava 时是调用的 collection.convert.Decorators 的

类似的,Scala 也在这里制造了同样的混乱,你可以用下面选用下面的方式来实现 JavaCoverters._ 的功能

总结一下,简单来讲就是两种方式来完成集合类型在 Scala 与 Java 间的转换

  1. collection.JavaConversions._ 自动转型,Scala 2.8 加入的。隐式转型可能会造成阅读上的障碍,可能会让人难以知晓什么变成了什么
  2. collection.JavaConverters._ 然后再调用 asJava/asScala 方法半自动转型(自动部分在生成 AsJava/AsScala 中间实例)。Scala 2.8.1 加入,稍新, asJava/asScala 为我们标记出了实际转型的地方,以及从哪个方向到哪个方向

注: collection.{JavaConversions, JavaConverters} 的全限名是 scala.collection.{JavaConversions, JavaConverters}}, 这是因为 Scala 中默认引入了 scala 包。

另外,等效的方式 collection.convert.wrapAll._, collection.convert.decorateAll._ 宜作暂忘

上面提及实例都是在 Scala 代码中如何完成集合在 Scala 与 Java 间又向转型,我们同样可以在 Java 代码中实现两种集合类型的转换。由于 Java 不可能用隐式转换,所以一切都是台面上的方法调用,看个例子:

显然,在 Java 中使用 Scala 集合类型这样转型有点蛋疼,所以使用 PlayFramework 的话,它自带了一个工具类 play.libs.Scala, 有少许转换方法可利用

play.libs.Scala

 

参考: 1. Conversions Between Java and Scala Collections
          2. What is the difference between JavaConverters and JavaConversions in Scala?
          3. Java <-> Scala Collection conversions, Scala 2.10 [duplicate] 
          4. Converting a Java collection into a Scala collection
          5. API scala.collection.JavaConverters

本文链接 https://yanbin.blog/scala-java-collections-cast/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments