2009-10-05 84 views
33

我正在学习Scala,我有一个Java项目迁移到斯卡拉。我想通过逐个重写类并检查新类没有破坏项目来迁移它。Java <->斯卡拉interop:透明列表和地图转换

该Java项目使用大量的java.util.Listjava.util.Map。在新的Scala课程中,我希望使用Scala的ListMap以获得好看的Scala代码。

问题是,新类(这些是Scala中的wtitten)没有与现有Java代码无差别地集成在一起:Java需要java.util.List,Scala需要它自己的scala.List

这是一个简化的问题示例。有类,逻辑,。他们互相呼叫: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); 
    } 
} 

在我的情况,班主要是框架类(我不需要迁移它们)。类逻辑是商业逻辑,将从Scala很酷的功能中受益匪浅。

我需要重写类逻辑 Scala中,同时保留与类主要完整性。最好重写会是什么样子(不工作):

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

理想行为:列表内Logic2是本地斯卡拉列出。全部进出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和Java之间的列表和地图的透明魔术转换 - > Scala(不需要做toScala/toJava)?

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

回答

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是未修改的(因为它确实是不可变的)。

  • 添加成员(方法,字段等)时,他说,你应该只定义隐式转换为两个目的一个

    豪尔赫·奥尔蒂斯说得最好。这些转换应该是与新的不相关的到范围内的任何其他类型。

  • “修复”破碎的类层次结构。因此,如果你有一些类型AB这是不相关的。你可以定义一个转换A => B if和只有如果你愿意有A <: B<:表示“子类型”)。

由于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

非常好,+1 – geowa4 2009-10-05 15:33:31

+1

(+1)非常好地陈述。我希望这篇文章已经出现,当我在一段时间后与之奋斗时。 – Shaun 2009-10-05 15:46:57

+1

感谢您的详细报道。通过谷歌找到了(当然!)所以已经救了一个人很多的痛苦! – 2009-10-15 06:59:36

3

下面是使用豪尔赫·奥尔蒂斯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

使用Scala 2.8,这是可以做到这样的:

import scala.collection.JavaConversions._ 

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