【问题标题】:How to instantiate a nested generic class defined in scala from java?如何从java实例化在scala中定义的嵌套泛型类?
【发布时间】:2018-07-27 20:56:08
【问题描述】:

我正在尝试从 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:错误:类 Outer.Inner 中的构造函数 Inner 不能应用于给定类型; Outer.Inner a = o.new Inner(); ^ 要求:外 发现:没有参数 原因:实际参数列表和形式参数列表的长度不同 其中 A 是一个类型变量: 在类 Outer.Inner 中声明的扩展对象 1 个错误

【问题讨论】:

  • java 部分仍然丢失......但它正在变得更好;)
  • 抱歉卡住了 SO 格式问题 :)
  • 奇怪的是,Outer.Inner&lt;String&gt; i = o.new Inner&lt;String&gt;(o); 似乎可以编译,但它会因一些“NoSuchMethodError”而崩溃。这很有趣......

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


【解决方案1】:

我不明白如何从 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 代码中使用纯 Java API

如果您不想实现完整的 Java API,您可以在本地使用这种方法来处理 Scala 代码中不能与您的 Java 项目无缝协作的部分。

为什么它应该起作用:Scala 总是试图适应 Java。 Java 完全忽略了 Scala。因此,要容易得多 从 Scala 调用 Java 代码,并在 Scala 中实现 Java 接口 Scala,而不是反过来。

以下是如何将此模式应用于您的示例(我对其进行了扩展以使其不平凡):

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

第 0 步:查看第三方 Scala 库

假设这是我们的第三方 Scala 库,那 有superImportantMethods 并计算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

附录一

(失败)尝试从 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”-文件:

反编译的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)。所以,我本质上想说的是:一切 崩溃和燃烧,我不知道为什么。因此,我会 只是建议实施上述解决方法。

希望对您有所帮助。

【讨论】:

    【解决方案2】:

    这对我来说似乎是一个错误。代码在没有类型参数的情况下编译得很好,但是当你添加它时,编译就会意外失败。由于某种原因,有了类型参数,javac 开始认为Outer.Inner 的构造函数需要两个Outer 类型的参数。因此,实际编译的代码是o.new Inner&lt;String&gt;(o),由于方法类型错误,它在运行时会失败。

    无论如何,除了反射调用构造函数之外,我想不出任何其他方法可以在 Java 中执行此操作。它不是很优雅,但这样我们至少可以将正确的参数传递给&lt;init&gt; 方法:

    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 节中的注释 涉及参数注释。

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

    【讨论】:

      【解决方案3】:

      您能否向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();
        }                                                                    
      }
      

      【讨论】:

      • 谢谢,但不幸的是,Scala 代码来自第三方库,我无法更改。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-18
      • 1970-01-01
      • 1970-01-01
      • 2016-06-08
      相关资源
      最近更新 更多