我试图从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
你能给外部
添加一个通用方法来实例化内部
实例吗?
外面的斯卡拉:
class Outer {
class Inner[A]
def inner[A](): Inner[A] = new Inner[A]
}
sctest。爪哇:
public class sctest {
public static void main(String[] args) {
Outer o = new Outer();
Outer.Inner<String> a = o.inner();
}
}
对我来说,这看起来像是一个bug。代码在没有type参数的情况下编译得很好,但是当您添加它时,编译就会意外失败。出于某种原因,当类型参数出现时,javac开始认为外层。内部
的构造函数需要两个类型为外层
的参数。因此,实际编译的代码是o.new内部
无论如何,除了反射式地调用构造函数之外,我想不出任何其他方法可以在Java中实现它。这不是很优雅,但这样我们至少可以将正确的参数传递给
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编译器似乎将隐式
外
参数从该签名中删除,因此当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描述符和签名属性之间的内部类不一致?(类文件)
引用JVMS4.7.9.1:
由签名属性编码的方法签名可能与方法信息结构中的方法描述符不完全对应(§4.3.3)。特别是,无法保证方法签名中形式参数类型的数量与方法描述符中参数描述符的数量相同。大多数方法的数字是相同的,但Java编程语言中的某些构造函数有一个隐式声明的参数,编译器用参数描述符表示该参数,但可能会从方法签名中忽略。有关涉及参数注释的类似情况,请参见§4.7.18中的注释。
这可能不是最一致的答案,但似乎这是指定的行为。
我不知道如何从Java实现这一点。详见附录。我会直接跳转到一个解决方案。
当您在试图从Java调用Scala代码时遇到Java Scala互操作问题时,有一个简单但可能有点繁重的解决方法,基本上适用于所有情况。
如果在Java代码中实例化Scala实体不起作用,请将所有内容隐藏在Java接口后面,并在Scala中实现该接口。
如果你正在使用一些主流的Scala框架,它可能有一个完全独立的JavaAPI(例如Spark,Akka,Play),那么请使用它!
如果它是一个鲜为人知的Scala软件包,没有单独的Java API,请执行以下操作:
如果不想实现完整的Java API,可以在本地使用这种方法,来处理Scala代码中与Java项目无法无缝工作的部分。
为什么它应该工作:Scala总是试图调整Java。Java完全忽略Scala。因此,从Scala调用Java代码并在Scala中实现Java接口要比反过来容易得多。
下面是如何将此模式应用到您的示例中(我对其进行了一些扩展,使其变得非同寻常):
注意类名有点难看,我这样做只是为了省略包声明,这样所有文件都可以在src/main/{scala,java}中转储;在实际实现中不要这样做。
第0步:查看第三方Scala库
假设这是我们的第三方Scala库,它有superImportantMethod
s并计算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方法或构造函数,通过定义一个干净的应用编程接口,您还可以保证不会遇到任何问题,因为一些Scala概念不能在Java中表示。事实上,它确实编译并运行:
[info] Running (fork) JavaMain
[info] foobarbaz42
我从这些定义开始(从你的问题中略为缩写的代码):
.斯卡拉:
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
构造函数,这已经有点可疑,而且不是很直观。
它编译了,但是当我试图运行它时,我得到了以下神秘的错误消息:
[错误]异常在线程"main"java.lang.NoSuchmetodError: A$B.(LA; LA;)V[错误]在JavaMain.main(JavaMain.java: 4)
我完全不知道这意味着什么,所以我反编译了生成的“.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类:
public class A.B<T> {
public /* synthetic */ A A$B$$$outer() {
return A.this;
}
public A.B() {
if (A.this == null) {
throw null;
}
}
}
反编译JavaMain。课程:
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);
}
}
新的A.B(a2,A)部分在我看来甚至不像是有效的java(对javac来说也是如此)。所以,我想说的是:一切都崩溃和燃烧,我不知道为什么。因此,我只建议实施上述解决方案。
希望这有帮助。
问题内容: 我使用,在我的应用程序中添加了易于阅读的配置文件,并试图在该文件周围添加包装器,以简化类型转换。具体来说,我希望返回的值从提供的默认值开始“继承”它的类型。到目前为止,这是我得到的: (完整示例来源。) 不管是否从属性文件中读取返回值,from的返回值都是一个布尔值,对于字符串,整数,双精度数和&c而言,返回值都类似。当然,上面的代码片段实际上并没有编译: 我做错了吗,还是我只是试图做
问题内容: 我想在Java中创建泛型类型的对象。请提出如何实现相同的建议。 注意:这似乎是一个简单的泛型问题。但是我打赌..不是。:) 假设我的类声明为: 问题答案: 你必须添加异常处理。 你必须在运行时传递实际类型,因为它不是编译后字节码的一部分,因此,没有显式提供它就无法知道它。
问题内容: 我知道Java的泛型在某种程度上逊于.Net。 我有一个泛型类,我确实需要使用无参数构造函数实例化。如何解决Java的局限性? 问题答案: 一种选择是传递(或你感兴趣的任何类型-以任何方式指定适当的引用)并将该值保留为字段: 另一种选择是具有“工厂”接口,然后将工厂传递给泛型类的构造函数。这更加灵活,你无需担心反射异常。
给定这样一个类 如何实现可以处理嵌套泛型类型的自定义Kafka反序列化器? 附注:我正在使用jackson进行序列化/反序列化。
问题内容: 我有这堂课 我正在尝试使用此方法在此类之外创建变量 这给了我这个错误 问题答案: 在实例化泛型时,应将其替换为相应的对象。 例如:
来自JavaScript背景,我发现以下代码有点过于健壮,包含多条语句;我想知道如何简化代码,并在一条语句中完成所有操作。 Student是超类,Friend和Schedule是聚合到超类ArrayList公共成员中的子类(这些不是嵌套类)。这是我当前的代码: 我想知道我是否可以做这样的事情,在一个语句中声明/实例化超类和子类;这可能吗?