Let's say we have the following class structure in Scala.
object Foo {
class Bar
}
We can easily construct Bar
in Java with new Foo.Bar()
. But everything changes when we add an extra level of nested classes.
object Foo {
object Bar {
class Baz
}
}
Somehow, it's no longer possible to construct the most inner class Baz
in Java. Looking at the javap
output, I can't see any significant difference between first (2 levels) and second cases (3 levels). Generated code looks pretty reasonable to me.
2 levels:
public class Foo$Bar { ... }
3 levels
public class Foo$Bar$Baz { ... }
With that said, what's the difference between 2-level vs. 3-level nested Scala classes when they accessed from Java?
Let's give the two versions different names to make them a little easier to talk about:
object Foo1 {
class Bar1
}
object Foo2 {
object Bar2 {
class Baz2
}
}
Now if you look at the class files, you'll see that the Scala compiler has created a Foo1
class. When you run javap -v
on Foo1$Bar1
, you'll see that that class is listed as the enclosing class:
InnerClasses:
public static #14= #2 of #13; //Bar1=class Foo1$Bar1 of class Foo1
This is exactly what would happen with a static nested class in Java, so the Java compiler is perfectly happy to compile new Foo1.Bar1()
for you.
Now look at the javap -v
output for Foo2$Bar2$Baz2
:
InnerClasses:
public static #16= #13 of #15; //Bar2$=class Foo2$Bar2$ of class Foo2
public static #17= #2 of #13; //Baz2=class Foo2$Bar2$Baz2 of class Foo2$Bar2$
Now the enclosing class is Foo2$Bar2$
, not Foo2$Bar2
(in fact the Scala compiler doesn't even generate a Foo2$Bar2
unless you add a companion class for object Bar2
). The Java compiler expects a static inner class Baz2
of a enclosing class Foo2$Bar2$
to be named Foo2$Bar2$$Baz2
, with two dollar signs. This doesn't match what it's actually got (Foo2$Bar2$Baz2
), so it says no to new Foo2.Bar2.Baz2()
.
Java is perfectly happy to accept dollar signs in class names, and in this case since it can't figure out how to interpret Foo2$Bar2$Baz2
as an inner class of some kind, it'll let you create an instance with new Foo2$Bar2$Baz2()
. So that's a workaround, just not a very pretty one.
Why does the Scala compiler treat Foo1
and Bar2
differently (in the sense that Bar2
doesn't get a Bar2
class), and why does the enclosing class listed in the InnerClasses
attribute for Baz2
have a dollar sign on the end, while the one for Bar1
doesn't? I don't really have any idea. But that's the difference—you just need a little more verbosity to see it with javap
.