33

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

这个 Java 项目使用了很多java.util.Listjava.util.Map. 在新的 Scala 类中,我想使用 ScalaListMap拥有好看的 Scala 代码。

问题是新类(那些在 Scala 中编写的)不能与现有的 Java 代码无缝集成:Java 需要java.util.List,Scala 需要自己的scala.List

这是问题的简化示例。有类MainLogicDao。他们在一行中互相调用:Main -> Logic -> Dao

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

在我的情况下,MainDao类是框架类(我不需要迁移它们)。Class Logic是业务逻辑,将从 Scala 的酷特性中受益匪浅。

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

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

理想行为:Logic2中的列表是原生 Scala 列表。所有进/出java.util.Lists都自动装箱/拆箱。但这不起作用。

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

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 <-> Scala 之间实现 Lists 和 Maps 的透明魔法转换(无需 toScala/toJava)?

如果不可能,迁移 Java -> 使用java.util.List和朋友的 Scala 代码的最佳实践是什么?

4

3 回答 3

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 说得最好,他说您应该只为以下两个目的之一定义隐式转换:

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

由于java.util.Map显然不是与我们层次结构中的任何东西无关的新类型,我们不能属于第一个附带条件。因此,我们唯一的希望是我们的转变Map[A, B] => java.util.Map[A, B]有资格获得第二次转变。但是,ScalaMap继承自java.util.Map. 它们实际上是完全正交的接口/特征。如上所示,试图忽略这些准则几乎总是会导致奇怪和意外的行为。

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

换句话说,使用asScalaandasJava 最佳实践。在生产应用程序中独立地走完这两条路后,我可以直接告诉您 javautils 方法更安全、更易于使用。不要仅仅为了节省 8 个字符而试图绕过它的保护!

于 2009-10-05T13:47:30.457 回答
3

以下是一些使用 Jorge Ortiz 的scalaj-collection 库的快速示例:

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 项目可从中央 maven 存储库获得

于 2009-11-12T04:55:19.840 回答
2

使用 Scala 2.8,可以这样完成:

import scala.collection.JavaConversions._

val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList
于 2012-05-28T18:04:48.720 回答