我试图从Java实例化嵌套的通用Scala类并遇到此编译错误。有人可以帮忙吗?感谢
class Outer {
class Inner[A]
}
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
答案 0 :(得分:2)
我不知道如何通过Java完成这项工作。详见附录。我将直接转到解决方案提案。
当你在尝试从Java调用Scala代码时遇到Java-Scala互操作问题时,有一个简单的,虽然可能有些重量级的解决方法,但它几乎适用于所有情况。
如果在Java代码中实例化Scala实体不起作用,则隐藏Java接口后面的所有内容,并在Scala中实现此接口。
如果你正在使用一些主流的Scala框架,那很可能 有一个完全独立的Java API(例如Spark,Akka,Play) 请使用!
如果它是一个鲜为人知的Scala软件包而没有单独的 Java API,执行以下操作:
如果您不想实现完整的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
我从这些定义开始(从您的问题中略微缩写代码):
.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();
}
}