Java <-> Scala 互操作:透明的列表和映射转换
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1519838/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java <-> Scala interop: transparent List and Map conversion
提问by oshyshko
I am learning Scala and I have a Java project to migrate to Scala. I want to migrate it by rewriting classes one-by-one and checking that new class didn't break the project.
我正在学习 Scala,我有一个要迁移到 Scala 的 Java 项目。我想通过一个一个地重写类并检查新类没有破坏项目来迁移它。
This Java project uses lots of java.util.Listand java.util.Map. In new Scala classes I would like to use Scala's Listand Mapto have good-looking Scala code.
这个 Java 项目使用了大量java.util.List和java.util.Map. 在新的 Scala 类中,我想使用 ScalaList并Map拥有漂亮的 Scala 代码。
The problem is that new classes (those are wtitten in Scala) do not integrate seamelessly with existing Java code: Java needs java.util.List, Scala needs its own scala.List.
问题在于新类(在 Scala 中包含的类)无法与现有 Java 代码无缝集成:Java 需要java.util.List,Scala 需要自己的scala.List.
Here is a simplified example of the problem. There are classes Main, Logic, Dao. They call each other in a line: Main -> Logic -> Dao.
这是问题的一个简化示例。有类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);
}
}
In my situation, classes Mainand Daoare framework classes (I don't need to migrate them). Class Logicis business-logic and will benefit a lot from Scala cool features.
在我的情况下,类Main和Dao是框架类(我不需要迁移它们)。类逻辑是业务逻辑,将从 Scala 的酷特性中受益匪浅。
I need to rewrite class Logicin Scala while preserving integrity with classes Mainand Dao. The best rewrite would look like (doesn't work):
我需要在 Scala 中重写Logic类,同时保持Main和Dao类的完整性。最好的重写看起来像(不起作用):
class Logic2 {
def calculate(ints: List[Integer]) : List[Integer] = {
val together: List[Integer] = new Dao().getSomeInts()
together ++ ints
}
}
Ideal behaviour: Lists inside Logic2are native Scala Lists. All in/out java.util.Listsget boxed/unboxed automagically. But this doesn't work.
理想行为:Logic2中的列表是原生 Scala 列表。所有进/出java.util.Lists自动装箱/拆箱。但这不起作用。
Instead, this does work (thanks to scala-javautils(GitHub)):
相反,这确实有效(感谢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
}
}
But it looks ugly.
但它看起来很丑。
How do I achieve transparent magic conversion of Lists and Maps between Java <-> Scala (without need to do toScala/toJava)?
如何在 Java <-> Scala 之间实现 Lists 和 Maps 的透明魔术转换(无需执行 toScala/toJava)?
If it is not possible, what are the best practices for migrating Java -> Scala code that uses java.util.Listand friends?
如果不可能,迁移 Java -> Scala 代码的最佳实践是java.util.List什么?
回答by Daniel Spiewak
Trust me; you don't wanttransparent conversion back and forth. This is precisely what the scala.collection.jcl.Conversionsfunctions attempted to do. In practice, it causes a lot of headaches.
相信我; 您不希望来回透明转换。这正是scala.collection.jcl.Conversions函数试图做的。在实践中,它会引起很多头痛。
The root of the problem with this approach is Scala will automatically inject implicit conversions as necessary to make a method call work. This can have some really unfortunate consequences. For example:
这种方法的问题的根源在于 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
}
This code wouldn't be entirely out of character for someone who is new to the Scala collections framework or even just the concept of immutable collections. Unfortunately, it is completely wrong. The result of this function is the samemap. The call to puttriggers an implicit conversion to java.util.Map<String, Int>, which happily accepts the new values and is promptly discarded. The original mapis unmodified (as it is, indeed, immutable).
对于不熟悉 Scala 集合框架甚至不可变集合概念的人来说,这段代码不会完全不合时宜。不幸的是,这是完全错误的。这个函数的结果是同一张图。调用put触发到 的隐式转换java.util.Map<String, Int>,它愉快地接受新值并立即被丢弃。原件map是未修改的(因为它确实是不可变的)。
Jorge Ortiz puts it best when he says that you should only define implicit conversions for one of two purposes:
Jorge Ortiz 说得最好,他说您应该只为以下两个目的之一定义隐式转换:
- Adding members (methods, fields, etc). These conversions should be to a new type unrelatedto anything else in scope.
- "Fixing" a broken class hierarchy. Thus, if you have some types
AandBwhich are unrelated. You may define a conversionA => Bif and onlyif you would have preferred to haveA <: B(<:means "subtype").
- 添加成员(方法、字段等)。这些转换应该是与范围内的任何其他内容无关的新类型。
- “修复”破碎的类层次结构。因此,如果您有一些类型
A并且B不相关。A => B当且仅当您希望拥有A <: B(<:表示“子类型”) 时,您才可以定义转换。
Since java.util.Mapis obviously not a new type unrelated to anything in our hierarchy, we can't fall under the first proviso. Thus, our only hope is for our conversion Map[A, B] => java.util.Map[A, B]to qualify for the second one. However, it makes absolutely no sense for Scala's Mapto inherit from java.util.Map. They are really completely orthogonal interfaces/traits. As demonstrated above, attempting to ignore these guidelines will almost always result in weird and unexpected behavior.
由于java.util.Map显然不是与我们的层次结构中的任何内容无关的新类型,因此我们不能属于第一个附带条件。因此,我们唯一的希望是我们的皈依Map[A, B] => java.util.Map[A, B]有资格参加第二次。然而,ScalaMap继承自java.util.Map. 它们实际上是完全正交的接口/特征。如上所述,试图忽略这些准则几乎总是会导致奇怪和意外的行为。
The truth is that the javautils asScalaand asJavamethods were designed to solve this exact problem. There is an implicit conversion (a number of them actually) in javautils from Map[A, B] => RichMap[A, B]. RichMapis a brand new type defined by javautils, so its only purpose is to add members to Map. In particular, it adds the asJavamethod, which returns a wrapper map which implements java.util.Mapand delegates to your original Mapinstance. This makes the process much more explicit and far less error prone.
事实是,javautilsasScala和asJava方法旨在解决这个确切的问题。在 javautils from 中有一个隐式转换(实际上有很多)Map[A, B] => RichMap[A, B]。 RichMap是一个由 javautils 定义的全新类型,所以它的唯一目的是将成员添加到Map. 特别是,它添加了asJava方法,该方法返回一个包装映射,该映射实现java.util.Map并委托给您的原始Map实例。这使得该过程更加明确且不易出错。
In other words, using asScalaand asJavaisthe best practice. Having gone down both of these roads independently in a production application, I can tell you first-hand that the javautils approach is much safer and easier to work with. Don't try to circumvent its protections merely for the sake of saving yourself 8 characters!
换句话说,使用asScala和asJava是最佳实践。在生产应用程序中独立地走完这两条道路后,我可以直接告诉您 javautils 方法更安全且更易于使用。不要仅仅为了拯救自己 8 个字符而试图绕过它的保护!
回答by David Carlson
Here are some quick examples using Jorge Ortiz's scalaj-collection library:
以下是一些使用 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]
the javautils project is available from the central maven repository
javautils 项目可从中央 maven 存储库中获得
回答by Somatik
With Scala 2.8, it could be done like this:
使用 Scala 2.8,可以这样做:
import scala.collection.JavaConversions._
val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList

