Java < - > Scala interop:透明的List和Map转换

osh*_*hko 33 java interop scala 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 - > 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

理想行为: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
  }
}
Run Code Online (Sandbox Code Playgroud)

但它看起来很难看.

如何实现Java < - > Scala之间的列表和映射的透明魔术转换(无需执行toScala/toJava)?

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

Dan*_*wak 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
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 添加成员(方法,字段等).这些转换应该是范围内任何其他内容无关的新类型.
  • "修复"一个破碎的类层次结构.因此,如果你有一些类型AB它们无关.A => B当且当您希望拥有A <: B(<:意味着"子类型")时,您才可以定义转换.

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

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

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

  • 注意,javautils已被取代.这些天你想要https://github.com/scalaj/scalaj-collection (2认同)