我正在学习 Scala,并且我有一个要迁移到 Scala 的 Java 项目。我想通过一个接一个地重写类并检查新类没有破坏项目来迁移它。
这个 Java 项目使用了很多 java.util.List
和 java.util.Map
。在新的 Scala 类中,我想使用 Scala 的 List
和 Map
来获得好看的 Scala 代码。
问题在于新类(那些在 Scala 中编写的)不能与现有 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);
}
}
在我的情况下,类 Main 和 Dao 是框架类(我不需要迁移它们)。 Logic 类是业务逻辑类,将从 Scala 的酷特性中受益匪浅。
我需要在 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-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
和 friend 的 Scala 代码的最佳做法是什么?
最佳答案
相信我;你不想要透明的来回转换。这正是 scala.collection.jcl.Conversions
试图做的功能。在实践中,它会引起很多麻烦。
这种方法的问题根源在于 Scala 会根据需要自动注入(inject)隐式转换以使方法调用正常工作。这可能会产生一些非常不幸的后果。例如:
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 集合框架甚至不可变集合概念的人来说,这段代码不会完全不合时宜。不幸的是,这是完全错误的。此函数的结果是 same 映射。调用put
触发到 java.util.Map<String, Int>
的隐式转换,它很高兴地接受了新值并立即被丢弃。原文map
是未修改的(因为它确实是不可变的)。
Jorge Ortiz 说得最好,他说您应该只为以下两个目的之一定义隐式转换:
- 添加成员(方法、字段等)。这些转换应该是一个新的类型无关范围内的任何其他东西。
- “修复”损坏的类层次结构。因此,如果你有一些类型
A
和B
这是无关的。您可以定义转换A => B
if 和 仅 如果您希望拥有A <: B
(<:
表示“子类型”)。
自从 java.util.Map
显然不是与我们层次结构中的任何东西无关的新类型,我们不能属于第一个附带条件。因此,我们唯一的希望是我们的转换Map[A, B] => java.util.Map[A, B]
获得第二名的资格。但是,对于 Scala 的 Map
来说绝对没有意义。从 java.util.Map
继承.它们实际上是完全正交的接口(interface)/特征。如上所示,试图忽略这些准则几乎总是会导致奇怪和意外的行为。
事实上,javautils asScala
和 asJava
方法旨在解决这个确切的问题。在 Map[A, B] => RichMap[A, B]
的 javautils 中有一个隐式转换(实际上有很多) . RichMap
是javautils定义的全新类型,所以它唯一的目的就是给Map
添加成员.特别是,它添加了 asJava
方法,它返回一个实现 java.util.Map
的包装器映射并代表您原来的Map
实例。这使得该过程更加明确且不易出错。
换句话说,使用 asScala
和 asJava
是最佳实践。在生产应用程序中独立地走完这两条路后,我可以直接告诉您 javautils 方法更安全且更易于使用。不要仅仅为了节省 8 个字符而试图绕过它的保护!
关于Java <-> Scala 互操作 : transparent List and Map conversion,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1519838/