如何实例化java中scala中定义的嵌套泛型类?

时间:2018-02-16 21:54:30

标签: java scala inner-classes scala-java-interop

我试图从Java实例化嵌套的通用Scala类并遇到此编译错误。有人可以帮忙吗?感谢

outer.scala:

class Outer {
    class Inner[A]
}

sctest.java:

public class sctest{
    public static void main(String[] args) {
        Outer o = new Outer();
        Outer.Inner<String> a = o.new Inner<String>();
    }
}

$ javac sctest.java

sctest.java:4: error: constructor Inner in class Outer.Inner cannot be applied to given types;
    Outer.Inner a = o.new Inner();
                              ^
  required: Outer
  found: no arguments
  reason: actual and formal argument lists differ in length
  where A is a type-variable:
    A extends Object declared in class Outer.Inner
 1 error

3 个答案:

答案 0 :(得分:2)

我不知道如何通过Java完成这项工作。详见附录。我将直接转到解决方案提案。

当你在尝试从Java调用Scala代码时遇到Java-Scala互操作问题时,有一个简单的,虽然可能有些重量级的解决方法,但它几乎适用于所有情况。

TL; DR

如果在Java代码中实例化Scala实体不起作用,则隐藏Java接口后面的所有内容,并在Scala中实现此接口。

变通方法提案

如果你正在使用一些主流的Scala框架,那很可能 有一个完全独立的Java API(例如Spark,Akka,Play) 请使用

如果它是一个鲜为人知的Scala软件包而没有单独的 Java API,执行以下操作:

  1. 在纯Java中写下一个简洁,干净的面向用户的API
  2. 在Java中提供面向框架的接口
  3. 在Scala中实现Java接口
  4. 提供Java API实现的入口点
  5. 在Java代码中使用pure-Java API
  6. 如果您不想实现完整的Java API,可以在本地使用此方法来处理与Java项目无法无缝协作的Scala代码部分。

    为什么它应该工作:Scala总是试图适应Java。 Java完全忽略了Scala。因此,更容易 从Scala调用Java代码,并在其中实现Java接口 斯卡拉,而不是相反。

    以下是这种模式如何应用于您的示例(我将其扩展了一点以使其变得非常重要):

    请注意,类名有点难看,我这样做只是为了省略包声明,所以所有文件都可以转储到src / main / {scala,java};不要在实际实施中这样做。

    第0步:查看第三方Scala库

    假设这是我们的第三方Scala库,那 有superImportantMethod并计算superImportantThings

    /* Suppose that this is the external Scala library 
     * that you cannot change.
     * A = Outer
     * B = Inner
     * 
     * + I added at least one member and one
     * method, so that it's not so boring and trivial
     */
    
    class A {
      var superImportantMemberOfA: Int = 42
      class B[T](t: T) {
        def superImportantMethod: String = t + "" + superImportantMemberOfA
      }
    }
    

    步骤1/2:用于Java项目的最小Java API

    我们将在java项目中使用这些接口,并实现它们 直接使用Scala:

    interface A_j {
      <X> B_j<X> createB(X x);
    }
    
    interface B_j<X> {
      String superImportantMethod();
    }
    

    第3步:在Scala中实现接口

    /** Implementation of the java api in 
      * Scala
      */
    class A_javaApiImpl extends A_j {
      private val wrappedA: A = new A
    
      private class B_javaApiImpl[X](val x: X) extends B_j[X] {
        private val wrappedB: wrappedA.B[X] = new wrappedA.B[X](x)
        def superImportantMethod: String = wrappedB.superImportantMethod
      }
    
      def createB[X](x: X): B_j[X] = new B_javaApiImpl[X](x)
    }
    

    第4步:提供API的入口点

    /** Some kind of entry point to the 
      * java API.
      */
    object JavaApi {
      def createA: A_j = new A_javaApiImpl
    }
    

    步骤5:在Java代码中使用仅Java的API:

    public class JavaMain {
      public static void main(String[] args) {
    
        // Use the Java API in your Java application
        // Notice that now all A_j's and B_j's are
        // pure Java interfaces, so that nothing 
        // should go wrong.
        A_j a = JavaApi.createA(); // the only call of a Scala-method.
        B_j<String> b = a.createB("foobarbaz");
        System.out.println(b.superImportantMethod());
      }
    }
    

    现在什么都不应该出错,因为Java(几乎)从不会调用任何东西 Scala方法或构造函数,以及通过定义干净的API 保证你不会遇到任何问题,因为 一些Scala概念无法用Java表示。确实如此 编译并运行:

     [info] Running (fork) JavaMain 
     [info] foobarbaz42
    

    附录I

    (失败)尝试从Java

    实例化通用内部Scala类

    我从这些定义开始(从您的问题中略微缩写代码):

    .scala:

    class A {
      class B[T]
    }
    

    的.java:

    public class JavaMain {
      public static void main(String[] args) {
        A a = new A();
        A.B<String> b = a.new B<String>(a);
      }
    }
    

    请注意,javac编译器需要A构造函数的B类型的参数, 这已经有点可疑了,而且不是很直观。

    它已编译,但当我尝试运行它时,我收到以下隐秘的错误消息:

    [error]线程“main”中的异常java.lang.NoSuchMethodError:A $ B.(LA; LA;)V [错误]在JavaMain.main(JavaMain.java:4)

    我完全不知道这是什么意思,所以我反编译生成的 的 “.class” -files:

    反编译A.class:

    public class A {
    
        public class B<T> {
            public /* synthetic */ A A$B$$$outer() {
                return A.this;
            }
    
            public B() {
                if (A.this == null) {
                    throw null;
                }
            }
        }
    
    }
    

    反编译A $ B.class:

    public class A.B<T> {
        public /* synthetic */ A A$B$$$outer() {
            return A.this;
        }
    
        public A.B() {
            if (A.this == null) {
                throw null;
            }
        }
    }
    

    反编译的JavaMain.class:

    public class JavaMain {
        public static void main(String[] arrstring) {
            A a;
            A a2 = a = new A();
            a2.getClass();
            A.B b = new A.B(a2, a);
        }
    }
    

    new A.B(a2, a) 部分对我来说甚至看起来都不是有效的java(也不是 javac)。所以,我本来想说的是:一切 崩溃和烧伤,我不知道为什么。因此,我愿意 只是建议实施上述解决方法。

    希望有所帮助。

答案 1 :(得分:1)

对我来说这看起来像个错误。代码编译没有type参数,但是当你添加它时,编译只是意外失败。出于某种原因,在存在类型参数的情况下,javac开始认为Outer.Inner的构造函数需要两个类型为Outer的参数。因此,实际编译的代码是o.new Inner<String>(o),由于方法类型错误而在运行时失败。

无论如何,除了反复调用构造函数之外,我想不出用Java做任何其他方法。它不是很优雅,但我们至少可以将正确的参数传递给<init>方法:

MethodHandle constructor = MethodHandles.lookup().findConstructor(Outer.Inner.class,
        MethodType.methodType(void.class, Outer.class));

Outer outer = new Outer();
Outer.Inner<String> inner = (Outer.Inner<String>) constructor.invokeExact(outer);

似乎是由存储通用方法类型信息的签名属性引起的。它在编译时用于检查使用正确的泛型参数调用方法,但据我所知,它在运行时没有用。 Java编译器似乎将隐式outer参数从此签名中删除,因此当Scala编译器执行相反操作时会出现混淆。

Java和Scala中的等效代码段具有不同的签名,同时具有相同的描述符(Ltest/Outer;Ljava/lang/Object;)V

// Java
package test;
class Outer { class Inner<A> {
    public Inner(A a) {}
    // Signature: (TA;)V
}}

// Scala
package test
class Outer { class Inner[A](a: A) {
  // Signature: (Ltest/Outer;TA;)V
}}

找到了一些相关的讨论:Java inner class inconsistency between descriptor and signature attribute? (class file)

引自JVMS 4.7.9.1。:

  

Signature属性编码的方法签名可能不会   完全对应于method_info中的方法描述符   结构(§4.3.3)。特别是,无法保证   方法签名中的形式参数类型的数量是相同的   作为方法描述符中的参数描述符的数量。该   对于大多数方法,数字是相同的,但中的某些构造函数   Java编程语言有一个隐式声明的参数   编译器用参数描述符表示,但可以省略   方法签名。有关类似情况,请参阅§4.7.18中的注释   涉及参数注释。

这可能不是最一致的答案,但似乎这是指定的行为。

答案 2 :(得分:0)

您可以向Outer添加通用方法来实例化Inner个实例吗?

outer.scala:

class Outer {
  class Inner[A]
  def inner[A](): Inner[A] = new Inner[A]
}

sctest.java:

public class sctest {
  public static void main(String[] args) {
    Outer o = new Outer();
    Outer.Inner<String> a = o.inner();
  }                                                                    
}