如何将 Scala 案例类和集合桥接到 Java

Avn*_*arr 3 java scala scala-java-interop

我们在尝试使用 Java 中的 Scala 代码时遇到问题。我们正在寻找一种方法来自动桥接各种 Scala 和 java 集合,而无需在我们的 Java 代码中进行显式转换。当地方很少时,这样做是合理的,但如果它广泛传播并不断添加新的 scala 类,它就会变得不切实际。

例如,在我们的 Scala 代码中,我们有一些这样的类(其中很多):

case class Product(id: String, group: String, attributes : Map[String,String])

def fooWithProducts(products : Seq[Product]) = ...

// create a product
val p1 = Product("id1","group1", Map("attribute1" -> "value1","attribute2" -> "value2")

val p2 = Product("id2","group1", Map("attribute1" -> "value1","attribute2" -> "value2")

fooWithProducts(Seq(p1,p2))
Run Code Online (Sandbox Code Playgroud)

当我们想用 Java 编写类似的代码时,它变得非常麻烦,我们需要在所有地方添加很多辅助样板:

// JAVA
// create a product
HashMap<String, String> attributes = new HashMap<String, String>();
attributes.put("my-attribute","my-value");

// create a scala collection
scala.collection.immutable.Map<String, String> scalaAttributes = Conversions.toScalaImmutableMap(attributes);

// create a product
Product p = Product("id","group",scalaAttributes)

// create a scala sequence of product
ArrayList<Product> dataFeedsProducts = new ArrayList<Product>();
        dataFeedsProducts.add(Product);
Seq<Product> seq = JavaConversions.asScalaBuffer(dataFeedsProducts).seq();

fooWithProducts(seq)

// SCALA helpers

public class Conversions {
    ///sf/ask/833221721/#11903737
    @SuppressWarnings("unchecked")
    /**
     * Transforms a java map to a scala immutable map
     */
    public static <K, V> scala.collection.immutable.Map<K, V> toScalaImmutableMap(java.util.Map<K, V> javaMap) {
        final java.util.List<scala.Tuple2<K, V>> list = new java.util.ArrayList(javaMap.size());
        for (final java.util.Map.Entry<K, V> entry : javaMap.entrySet()) {
            list.add(scala.Tuple2.apply(entry.getKey(), entry.getValue()));
        }
        final scala.collection.Seq<Tuple2<K, V>> seq = scala.collection.JavaConverters.asScalaBufferConverter(list).asScala().toSeq();
        return (scala.collection.immutable.Map<K, V>) scala.collection.immutable.Map$.MODULE$.apply(seq);
    }

    /**
     * transforms a scala map to the java counterpart
     */
    public static <K,V> java.util.Map<K,V> mapAsJavaMap(scala.collection.immutable.Map<K,V> m) {
        if (m == null) {
            return null;
        }
        return JavaConversions.mapAsJavaMap(m);
    }

    public static<A> Seq<A> asScalaSeq(java.util.List<A> l) {
        return JavaConversions.asScalaBuffer(l).toSeq();
    }
}
Run Code Online (Sandbox Code Playgroud)

我们想知道是否有一种方法可以为这些 Scala 习语注释 Scala 代码或自动生成等效的 Java 对应接口。

And*_*kin 6

大多数用 Scala 编写但应该在 Scala 和 Java 中使用的框架都有一个单独的 Java API。例如,Akka、Play 和 Spark 就是这种情况。

这样做是因为有太多的 Scala 构造不能自然地映射到 Java 中可用的任何内容。虽然从 Java 实例化 Scala 类并非完全不可能,但直接这样做会导致非常不习惯和繁琐的 Java 代码。因此,设计人员并没有将将 Java 数据结构转换为 Scala 数据结构的负担转移给 API 的用户,而是简单地创建一个单独的 Java API,并一劳永逸地解决所有转换和工厂方法的问题。

也就是说,而不是

  • 在 Scala 中编写一个库并仅提供 Scala API
  • 在 Java 中编写类 C_1,使用 Scala API,通过转换污染代码(painful-1)
  • ...
  • 在 Java 中编写类 C_n,使用 Scala API,通过转换污染代码(painful-n)

您执行以下操作:

  • 用Scala写一个库,先提供Scala API
  • foobar.api.java向你的库中添加一个包,实现一个单独的 Java API,仍然使用 Scala (pain-singleton)
  • 使用 Java API 在 Java 中编写类 C_1 (nice-and-pleasant-1)
  • ...
  • 使用 Java API 在 Java 中编写类 C_n (nice-and-pleasant-n)

这应该会大大减少您对 Scala 库的用户(在您的情况下:对您自己)造成的总体痛苦。

对于您提供的小代码片段,这具体意味着:

  1. 提供工厂方法,以便Product可以方便地从 Java 实例化 s:

    // 在 api.java 中的某处 def product( id: String, grp: String, attrToVal: java.util.HashMap[String, String] ): Product = /* use JavaConversions, invoke constructor */

  2. 提供一个fooWithProducts可以从 Java 调用而无需任何转换的版本:

    // 在 api.java def fooWithProducts(ps: java.util.List[Product]): Foo = // 在这里将 java List 转换为 scala Seq, // 调用原始 fooWithProducts

对应该出现在 API 中的每个类和方法执行此操作。为了使其正常工作,API 必须具有最小的表面积,并且与库的内部结构相比,它的重点和相对简单。