Доступ к Scala вложенным классам из Java

Скажем, у нас есть следующая структура класса в Scala.

object Foo {
  class Bar
}

Мы можем легко построить Bar в Java с помощью new Foo.Bar(). Но все меняется, когда мы добавляем дополнительный уровень вложенных классов.

object Foo {
  object Bar {
    class Baz
  }
}

Как-то больше невозможно построить самый внутренний класс Baz в Java. Глядя на вывод javap, я не вижу существенной разницы между первым (2 уровня) и вторым случаем (3 уровня). Сгенерированный код выглядит довольно разумным для меня.

2 уровня:

public class Foo$Bar { ... }

3 уровня

public class Foo$Bar$Baz { ... }

С учетом сказанного, какая разница между 2-уровневыми и 3-уровневыми вложенными классами Scala при доступе к ним из Java?

Ответ 1

Давайте дадим двум версиям разные имена, чтобы их было немного легче говорить:

object Foo1 {
  class Bar1
}

object Foo2 {
  object Bar2 {
    class Baz2
  }
}

Теперь, если вы посмотрите на файлы классов, вы увидите, что компилятор Scala создал класс Foo1. Когда вы запустите javap -v на Foo1$Bar1, вы увидите, что этот класс указан как класс-оболочка:

InnerClasses:
     public static #14= #2 of #13; //Bar1=class Foo1$Bar1 of class Foo1

Это именно то, что произойдет со статическим вложенным классом в Java, поэтому компилятор Java с удовольствием компилирует new Foo1.Bar1() для вас.

Теперь посмотрите на вывод javap -v для 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$

Теперь охватывающий класс Foo2$Bar2$, а не Foo2$Bar2 (на самом деле компилятор Scala даже не генерирует Foo2$Bar2, если вы не добавите класс-компаньон для object Bar2). Компилятор Java ожидает, что статический внутренний класс Baz2 охватывающего класса Foo2$Bar2$ будет называться Foo2$Bar2$$Baz2 с двумя знаками доллара. Это не соответствует тому, что было на самом деле (Foo2$Bar2$Baz2), поэтому оно не соответствует new Foo2.Bar2.Baz2().

Java отлично согласна принимать знаки доллара в именах классов, и в этом случае, поскольку он не может понять, как интерпретировать Foo2$Bar2$Baz2 как внутренний класс какого-то рода, он позволит вам создать экземпляр с new Foo2$Bar2$Baz2(). Так что обходной путь, просто не очень красивый.

Почему компилятор Scala обрабатывает Foo1 и Bar2 по-разному (в том смысле, что Bar2 не получает класс Bar2) и почему класс включения, указанный в InnerClasses атрибут для Baz2 имеет знак доллара на конце, а для Bar1 - нет? Я понятия не имею. Но эта разница - вам просто нужно немного больше подробностей, чтобы увидеть ее с помощью javap.