用于 Scala 到 Java 集合转换的高性能包装器

Uko*_* Ra 1 java performance scala

假设我有一个带有一些业务逻辑的 Java 类:

package examples;

import java.util.List;
import java.util.Map;

public class Inner {
    public void consume(Map<Integer, List<Double>> map) {
        map.forEach((k, v) -> {
            System.out.println("Key: " + k);
            v.forEach(i -> System.out.println("  item: " + i));
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我想编写一个高性能的、尽可能原生的Scala 包装器(因为这个包装器可能会被高频调用),所以:

第一次尝试

package examples

class Wrapper(val asJava: Inner) extends AnyVal {
  implicit def consume(map: Map[Int, List[Double]]): Unit = asJava.consume(map)
}
Run Code Online (Sandbox Code Playgroud)

但得到错误:

[error]  found   : Map[Int,List[Double]]               (in scala.collection.immutable)
[error]  required: Map[Integer,java.util.List[Double]] (in java.util)
[error]   implicit def consume(map: Map[Int, List[Double]]): Unit = asJava.consume(map)
[error]                                                                            ^
[error] one error found
Run Code Online (Sandbox Code Playgroud)

第二次尝试

[error]  found   : Map[Int,List[Double]]               (in scala.collection.immutable)
[error]  required: Map[Integer,java.util.List[Double]] (in java.util)
[error]   implicit def consume(map: Map[Int, List[Double]]): Unit = asJava.consume(map)
[error]                                                                            ^
[error] one error found
Run Code Online (Sandbox Code Playgroud)

仍然有错误:

[error]  found   : java.util.Map[Int,List[scala.Double]]
[error]  required: java.util.Map[Integer,java.util.List[java.lang.Double]]
[error]   implicit def consume(map: Map[Int, List[Double]]): Unit = asJava.consume(map.asJava)
[error]                                                                                ^
[error] one error found
Run Code Online (Sandbox Code Playgroud)

而且恐怕scala.jdk.CollectionConverters._会分配额外的内存,浪费很多时间

问题

  1. 我可以scala.jdk.CollectionConverters._在高性能场景中使用吗?
  2. 在 Kotlin 中,所有原始类型和大多数集合都可以在几乎没有性能损失的情况下转换为/从它们的 Java 替代方案转换,所以我想知道如何在 Scala 中实现目标?

Mik*_*len 5

更新 1:我已经扩展了答案以提供更多细节和解释。

Scala定义中使用Integer而不是Int,而java.lang.Double不是scala.Double(这是DoubleScala 中被解释的)。

问题是Java不允许在集合中使用原语,这就是为什么它使用Integer而不是int作为类型,而Double不是double. 在ScalaIntDouble、 等的处理更为复杂。在大多数情况下,它会将它们视为原语(即,像Javaintdouble等),但在与集合一起使用时会将它们装箱(和拆箱)。

但是,在与Java互操作时,需要更加明确。ScalaInteger在必要时将实例隐式转换为Int实例,反之亦然,但它不能隐式转换装箱原语的集合。

您可以在Scala 中很好地使用Java集合,而且开销为零;您根本不需要将它们转换为Scala等效项。但是,如果您将转换传递给需要Scala等效集合的代码,或者如果您想将Scala集合与遗留Java代码一起使用,您显然需要执行转换。(如果它是不明确的,一个是不一样的一个,也不是一个相同的作为该Scala的集合是不可变的,并设计用于使用有效使用功能的编程范例。)java.util.Mapscala.collection.immutable.Mapjava.util.Listscala.collection.immutable.List

如果您担心性能,则应该使用微基准测试工具包,例如ScalaMeter,它可以让您准确地衡量本地使用Java集合与转换为/从Scala集合之间的比较。

更新 2

我已经使用Java的集合重新编写了您的第一次尝试。为了避免与Scala类型混淆,我通过在它们前面加上前缀来重命名冲突的Java类型J

package examples

import java.lang.{Double => JDouble}
import java.util.{List => JList, Map => JMap}
import scala.language.implicitConversions

class Wrapper(val asJava: Inner) {
  implicit def consume(map: JMap[Integer, JList[JDouble]]): Unit = {
    asJava.consume(map)
  }
}
Run Code Online (Sandbox Code Playgroud)

这使用 JavaMapList集合,以及装箱原语。它可以编译,但它绝对不会给您带来任何好处( 的签名与Wrapper.consume完全相同Inner.consume)。但是,它确实说明了如何在Scala中原位使用Java集合。

如果你想使用Scala集合,并转换为Java等价物,我认为这是你第二次尝试的意图,那么它看起来像这样:

package examples

import java.lang.{Double => JDouble}
import scala.jdk.CollectionConverters._
import scala.language.implicitConversions

class Wrapper(val asJava: Inner) {
  implicit def consume(map: Map[Int, List[Double]]): Unit = {
    val jmap = map.asInstanceOf[Map[Integer, List[JDouble]]]
    asJava.consume(jmap.map(p => p._1 -> p._2.asJava).asJava)
  }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们首先必须将集合转换为使用装箱类型(定义在引擎盖下是等效的),这样没有开销。

然后我们必须将Scala数据结构转换为等效的Java数据结构。如果只是将Scala数据结构包装在Java接口中,这样做显然会产生开销。

更新 3

我还应该指出,制作这种Wrapper.consume方法implicit是一个奇怪的选择。我认为您可能打算创建 class implicit,以便(在第二次尝试中)您可以使用实例隐式使用Scala数据结构Inner。(实际上,InnerWrapper. 中提供的函数装饰。)Inner实例将被隐式转换为Wrapper实例,没有开销(这Wrapper在大多数情况下甚至不会创建实例,因为有extending AnyVal子句):

import java.lang.{Double => JDouble}
import scala.jdk.CollectionConverters._
import scala.language.implicitConversions

package object examples {
  implicit class Wrapper(val asJava: Inner) extends AnyVal {
    def consume(map: Map[Int, List[Double]]): Unit = {
      val jmap = map.asInstanceOf[Map[Integer, List[JDouble]]]
      asJava.consume(jmap.map(p => p._1 -> p._2.asJava).asJava)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,Wrapper现在需要在package object. (这是因为隐式类只能在object某种类型的an中定义,必须将其引入范围,而 apackage object通常是实现此目的的最方便的方法。)