Java< - > Scala interop:透明列表和地图转换

时间:2009-10-05 12:40:34

标签: java scala interop scala-java-interop

我正在学习Scala,我有一个Java项目要迁移到Scala。我希望通过逐个重写类并检查新类没有破坏项目来迁移它。

此Java项目使用了大量java.util.Listjava.util.Map。在新的Scala类中,我想使用Scala的ListMap来获得漂亮的Scala代码。

问题是新类(Scala中的wtitten)没有与现有Java代码无缝集成:Java需要java.util.List,Scala需要自己的scala.List

以下是问题的简化示例。有类 Main Logic Dao 。他们互相称呼: Main - >逻辑 - >道

public class Main {
    public void a() {
        List<Integer> res = new Logic().calculate(Arrays.asList(1, 2, 3, 4, 5));
    }
}

public class Logic {
    public List<Integer> calculate(List<Integer> ints) {
        List<Integer> together = new Dao().getSomeInts();
        together.addAll(ints);
        return together;
    }
}

public class Dao {
    public List<Integer> getSomeInts() {
        return Arrays.asList(1, 2, 3);
    }
}

在我的情况下,类 Main Dao 是框架类(我不需要迁移它们)。类逻辑是业务逻辑,将从Scala cool功能中获益良多。

我需要在Scala中重写类 Logic ,同时保留类 Main Dao 的完整性。最好的重写看起来像(不起作用):

class Logic2 {
  def calculate(ints: List[Integer]) : List[Integer] = {
      val together: List[Integer] = new Dao().getSomeInts()
      together ++ ints
  }
}

理想行为: Logic2 中的列表是本机Scala列表。全进/出java.util.Lists自动装箱/取消装箱。但这不起作用。

相反,这确实有效(感谢scala-javautilsGitHub)):

import org.scala_tools.javautils.Implicits._

class Logic3 {
  def calculate(ints: java.util.List[Integer]) : java.util.List[Integer] = {
      val together: List[Integer] = new Dao().getSomeInts().toScala
      (together ++ ints.toScala).toJava
  }
}

但它看起来很难看。

如何在Java&lt; - &gt;之间实现列表和地图的透明魔术转换Scala(无需执行toScala / toJava)?

如果不可能,迁移Java的最佳做法是什么?&gt;使用java.util.List和朋友的Scala代码?

3 个答案:

答案 0 :(得分:65)

相信我;你没有希望来回透明转换。这正是scala.collection.jcl.Conversions函数试图做的事情。在实践中,它会引起很多麻烦。

这种方法的问题根源是Scala会根据需要自动注入隐式转换,以使方法调用工作。这可能会产生一些非常不幸的后果。例如:

import scala.collection.jcl.Conversions._

// adds a key/value pair and returns the new map (not!)
def process(map: Map[String, Int]) = {
  map.put("one", 1)
  map
}

对于不熟悉Scala集合框架或甚至只是不可变集合概念的人来说,这段代码并不完全不符合要求。不幸的是,这是完全错误的。此函数的结果是相同的映射。对put的调用会触发隐式转换为java.util.Map<String, Int>,它会愉快地接受新值并立即被丢弃。原始map未经修改(因为它确实是不可变的)。

Jorge Ortiz说得最好,他说你应该只为两个目的之一定义隐式转换:

  • 添加成员(方法,字段等)。这些转换应该是与无关的新类型到范围内的任何其他内容。
  • “修复”损坏的类层次结构。因此,如果您有一些不相关的AB类型。如果您希望A => BA <: B表示“子类型”),则可以定义转化<: if和

由于java.util.Map显然不是与我们的层次结构中的任何内容无关的新类型,因此我们不能归入第一个附带条件。因此,我们唯一的希望是我们的转换Map[A, B] => java.util.Map[A, B]有资格获得第二个。但是,Scala的Map继承java.util.Map绝对没有意义。它们实际上是完全正交的接口/特征。如上所述,试图忽略这些指导方针几乎总会导致奇怪和意外的行为。

事实是,javautils asScalaasJava方法旨在解决这个问题。在Map[A, B] => RichMap[A, B]的javautils中有一个隐式转换(实际上是其中一些转换)。 RichMap是由javautils定义的全新类型,因此其唯一目的是将成员添加到Map。特别是,它添加了asJava方法,该方法返回一个包装器映射,该映射实现java.util.Map并委托给原始Map实例。这使得该过程更加明确,并且更不容易出错。

换句话说,使用asScalaasJava 的最佳做法。在生产应用程序中独立地沿着这两条道路走下去,我可以直接告诉你javautils方法更安全,更容易使用。不要仅仅为了节省自己8个字符而试图绕过它的保护!

答案 1 :(得分:3)

以下是使用Jorge Ortiz的scalaj-collection library

的一些简单示例
import org.scala_tools.javautils.Implicits._

val sSeq = java.util.Collections.singletonList("entry") asScala
// sSeq: Seq[String] 
val sList = sSeq toList // pulls the entire sequence into memory
// sList: List[String]
val sMap = java.util.Collections.singletonMap("key", "value") asScala
// sMap: scala.collection.Map[String, String]

val jList = List("entry") asJava
// jList: java.util.List[String]
val jMap = Map("key" -> "value") asJava
// jMap: java.util.Map[String, String]

javautils项目可从central maven repository

获得

答案 2 :(得分:2)

使用Scala 2.8,可以这样做:

import scala.collection.JavaConversions._

val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList
相关问题