从Java调用多态Scala方法

时间:2011-11-17 17:42:02

标签: java scala interop polymorphism

我正在编写一个我想要从Scala和Java中使用的API。我有一个在Scala类中定义的多态方法,我在用Java调用它时遇到了麻烦。

根据scala-lang.org上的这种互操作性FAQ,使用使用高级语言功能的Scala类“可能很棘手”,但它并不是说不可能。由于Scala的高级功能如何转换为Java没有记录,因此它建议反汇编类文件以试验它的工作原理。

示例代码

class Polymorphic {
  def polyMethod[T](implicit om: Manifest[T]) = {
    println(om.erasure.getName)
    om.erasure.newInstance
  }
}

object Polymorphic {
  def main(args: Array[String]) {
    val objOfT = (new Polymorphic).polyMethod[String]
  }
}

这在scala中正常工作,打印“java.lang.String

我在生成的类文件上运行了javap -c并获得了polyMethod的以下列表:

public java.lang.Object polyMethod(scala.reflect.Manifest);
  Code:
   0:   getstatic   #20; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   aload_1
   4:   invokeinterface #27,  1; //InterfaceMethod scala/reflect/ClassManifest.erasure:()Ljava/lang/Class;
   9:   invokevirtual   #33; //Method java/lang/Class.getName:()Ljava/lang/String;
   12:  invokevirtual   #37; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   15:  aload_1
   16:  invokeinterface #27,  1; //InterfaceMethod scala/reflect/ClassManifest.erasure:()Ljava/lang/Class;
   21:  invokevirtual   #41; //Method java/lang/Class.newInstance:()Ljava/lang/Object;
   24:  areturn

3个主要问题:

  1. 该方法的返回类型是多态的
  2. {<1}}参数未隐含在Java
  3. 我实际上无法传递类型参数。
  4. 我正在尝试使用的真实项目代码在这里: https://github.com/ConnorDoyle/EnMAS/blob/master/clientServerPrototype/src/org/enmas/pomdp/State.scala

    我的最终目标是完全通用但类型安全的数据字典。它在Scala中运行良好,但我真的希望能够从Java API调用此方法。我的设计动机是保持客户端代码中的反射,显式转换和空检查。

    编辑:2011年11月18日 回应以下@kassens的评论

    Manifest的定义更改为以下内容:

    polyMethod
    当我用def polyMethod[T](implicit om: Manifest[T]): T = { println(om.erasure.getName) om.erasure.newInstance.asInstanceOf[T] } 检查类文件时,

    给出完全相同的字节代码。

4 个答案:

答案 0 :(得分:4)

有几个问题在起作用:

  1. Scala中的统一泛型。事件T在Scala中是无限的(意味着它可以用基本类型实例化),这使得Scala编译器保守地将其擦除为Object。如果您将scala代码更改为

    def polyMethod[T <: AnyRef](implicit om: Manifest[T]) = {
       println(om.erasure.getName)
       om.erasure.newInstance.asInstanceOf[T]
    }
    

    它会返回T.注意newInstance的显式转换,因为Scala库方法没有返回正确的泛型类型。

  2. 使用清单。这会增加您需要对齐的另一层泛型,以便使您的呼叫通过。正如didierd所建议的那样,更好的是Scala中的重载需要java.lang.Class
  3. 所以我的建议是在你的Scala代码中添加它:

      def polyMethod[T <: AnyRef](cls: Class[T]): T = 
        polyMethod(Manifest.classType(cls))
    

    然后从Java中调用它:

    void test(Polymorphic p) { 
        JavaScalaCall c = p.polyMethod(this.getClass());
    }
    

    作为旁注,字节码总是被擦除。如果要查看类文件中的通用信息,请使用例如jclasslib查找“签名”属性。

答案 1 :(得分:2)

java中具有manifest参数的等价物是传递类(显式唉,当你想要的类型是泛型类型时,它不能很好地工作)。

可以从java创建清单,但最好从scala中创建。 你可以添加

def polyMethodForJava[T](clazz: Class[T]) 
  = polyMethod(ClassManifest.From(clazz))

当然,使用它并不像scala那样舒适。

答案 2 :(得分:2)

肯定有一些Scala方法无法从Java调用。有关一个示例(不是您遇到过的示例)的说明,请参阅here

通常,如果要创建一个可以由Java客户端“自然”使用的接口,您可能需要编写一些适配器类或方法。

答案 3 :(得分:0)

一个建议是使用Java泛型类型,Box / Unbox是您自己的数据类型。使用类型匹配来执行操作。