Java <-> Scala 互操作:透明的列表和映射转换

2022-09-01 08:15:54

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

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

问题在于,新类(在Scala中是wtitten的)不会与现有的Java代码无缝集成:Java需要,Scala需要自己的。java.util.Listscala.List

下面是该问题的简化示例。有类逻辑。他们用一行互相称呼:主->逻辑->道

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是框架类(我不需要迁移它们)。类逻辑是业务逻辑,将从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-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<>Scala(无需执行toScala/toJava)之间实现列表和地图的透明魔术转换?

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


答案 1

相信我;你不希望来回转换透明。这正是这些函数试图做的事情。在实践中,它会引起很多头痛。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 集合框架甚至只是不可变集合概念的人来说,这段代码不会完全脱离角色。不幸的是,这是完全错误的。此函数的结果是相同的映射。对 的调用将触发 对 的隐式转换,该转换愉快地接受新值并立即被丢弃。原件是未经修改的(因为它确实是不可变的)。putjava.util.Map<String, Int>map

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

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

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

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

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


答案 2

以下是使用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 存储库获得


推荐