I'm trying to instantiate a nested generic Scala class from Java and running into this compilation error. Can someone help? Thanks
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
I don't see how this can be done from Java. See appendix for details. I'll jump directly to a workaround proposal.
Whenever you are facing problems with Java-Scala interop while trying to call Scala code from Java, there is a simple, while maybe somewhat heavyweight workaround, that works in essentially every situation.
###TL;DR
If instantiating Scala entities in Java code does not work, hide everything behind a Java-interface, and implement this interface in Scala.
###Workaround proposal
If you are using some mainstream Scala framework, it probably has a completely separate Java API (e.g. Spark, Akka, Play), then please use that!
If it is a less known Scala software package without a separate Java API, do the following:
If you don't want to implement a full Java API, you can use this approach locally, to deal with those parts of Scala code that don't work seamlessly with your Java project.
Why it should work: Scala can easily express everything that Java can express, whereas Java has no mechanisms for interpreting most of the Scala constructs (the entire metaprogramming subsystem - given
s/using
/derives
/inline
etc., as well as the whole type-inference machinery, simply have no corresponding counterparts on the javac
side). Therefore, it's much easier to implement Java-interfaces in Scala than using any Scala code from Java.
Here is how this pattern can be applied to your example (I extended it a bit to make it non-trivial):
Note that class names are somewhat ugly, I did that only to omit the package declarations, so that all the files can be dumped in src/main/{scala,java}; Don't do this in real implementation.
Step 0: Look at the third-party Scala library
Suppose that this is our third-party Scala library, that
has superImportantMethod
s and computes 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
}
}
Step 1/2: Minimal Java API for use in your Java project
We will use these interfaces in our java project, and implement them using Scala directly:
interface A_j {
<X> B_j<X> createB(X x);
}
interface B_j<X> {
String superImportantMethod();
}
Step 3: Implement the interfaces in 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)
}
Step 4: provide an entry point to the API
/** Some kind of entry point to the
* java API.
*/
object JavaApi {
def createA: A_j = new A_javaApiImpl
}
Step 5: Use the java-only API in your Java code:
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());
}
}
Now nothing should go wrong, because Java (almost) never calls any Scala methods or constructors, and by defining a clean API you also have a guarantee that you will not run into any problems because some Scala concepts cannot be represented in Java. Indeed, it does compile and run:
[info] Running (fork) JavaMain
[info] foobarbaz42
###Appendix I ###(Failed) attempt to instantiate generic inner Scala classes from Java
I started with these definitions (slightly abbreviated code from your question):
.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);
}
}
Notice that the javac compiler required a parameter of type A
for the B
constructor,
which already was somewhat suspicious, and not really intuitive.
It compiled, but when I tried to run it, I got the following cryptic error message:
[error] Exception in thread "main" java.lang.NoSuchMethodError: A$B.(LA;LA;)V [error] at JavaMain.main(JavaMain.java:4)
I had absolutely no clue what this is supposed to mean, so I decompiled the generated ".class"-files:
Decompiled 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;
}
}
}
}
Decompiled 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;
}
}
}
Decompiled 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);
}
}
The new A.B(a2, a)
part doesn't even look like valid java to me (and neither to
the javac). So, what I'm essentially trying to say: everything
crashes and burns and I don't know why. Therefore, I would
simply advise to implement the workaround described above.
Hope that helps.