Accessing Scala nested classes from Java

2020-06-09 04:12发布

问题:

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?

回答1:

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.



标签: java scala