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

发布于 2024-08-06 22:24:34 字数 2053 浏览 4 评论 0 原文

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

此 Java 项目使用大量 java.util.Listjava.util.Map。在新的 Scala 类中,我想使用 Scala 的 ListMap 来获得美观的 Scala 代码。

问题是新类(那些在 Scala 中编写的)不能与现有 Java 代码无缝集成:Java 需要 java.util.List,Scala 需要自己的 scala.List >。

这是问题的一个简化示例。有类MainLogicDao。他们排成一行互相称呼:Main ->逻辑->道

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 是框架类(我不需要迁移它们)。 Logic 类是业务逻辑,将从 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 的最佳实践是什么 ->使用 java.util.List 的 Scala 代码和朋友?

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.

This Java project uses lots of java.util.List and java.util.Map. In new Scala classes I would like to use Scala’s List and Map to have good-looking Scala code.

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.

Here is a simplified example of the problem. There are classes Main, Logic, Dao. They call each other in a line: 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 Main and Dao are framework classes (I don’t need to migrate them). Class Logic is business-logic and will benefit a lot from Scala cool features.

I need to rewrite class Logic in Scala while preserving integrity with classes Main and Dao. The best rewrite would look like (doesn’t work):

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

Ideal behaviour: Lists inside Logic2 are native Scala Lists. All in/out java.util.Lists get boxed/unboxed automagically. But this doesn't work.

Instead, this does work (thanks to 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)?

If it is not possible, what are the best practices for migrating Java -> Scala code that uses java.util.List and friends?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

醉态萌生 2024-08-13 22:24:34

相信我;您不想要来回透明的转换。这正是 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 的隐式转换,它会愉快地接受新值并立即丢弃。原始的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 个字符而尝试规避其保护!

Trust me; you don't want transparent conversion back and forth. This is precisely what the scala.collection.jcl.Conversions functions attempted to do. In practice, it causes a lot of headaches.

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:

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 same map. The call to put triggers an implicit conversion to java.util.Map<String, Int>, which happily accepts the new values and is promptly discarded. The original map is unmodified (as it is, indeed, immutable).

Jorge Ortiz puts it best when he says that you should only define implicit conversions for one of two purposes:

  • Adding members (methods, fields, etc). These conversions should be to a new type unrelated to anything else in scope.
  • "Fixing" a broken class hierarchy. Thus, if you have some types A and B which are unrelated. You may define a conversion A => B if and only if you would have preferred to have A <: B (<: means "subtype").

Since java.util.Map is 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 Map to 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.

The truth is that the javautils asScala and asJava methods 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]. RichMap is a brand new type defined by javautils, so its only purpose is to add members to Map. In particular, it adds the asJava method, which returns a wrapper map which implements java.util.Map and delegates to your original Map instance. This makes the process much more explicit and far less error prone.

In other words, using asScala and asJava is the 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!

潜移默化 2024-08-13 22:24:34

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

Here are some quick examples using Jorge Ortiz's 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]

the javautils project is available from the central maven repository

风渺 2024-08-13 22:24:34

使用 Scala 2.8,可以这样完成:

import scala.collection.JavaConversions._

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

With Scala 2.8, it could be done like this:

import scala.collection.JavaConversions._

val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文