Имя класса Java, содержащее знак доллара, не может быть скомпилировано, если присутствует внутренний класс

У меня есть следующие классы Java:

mac-grek:javajunk grek$ cat A\$B.java
class A$B {}
mac-grek:javajunk grek$ cat A.java
public class A {
  public static class B {}
}
mac-grek:javajunk grek$ cat Main.java 
public class Main {

  public static void main(String[] args) {
    System.out.println(A.B.class.getName());
    System.out.println(A$B.class.getName());
  }

}

Когда я пытаюсь скомпилировать их, я получаю следующие ошибки:

mac-grek:javajunk grek$ javac 'A$B.java' A.java Main.java
A.java:2: duplicate class: A.B
  public static class B {}
                ^
Main.java:4: cannot find symbol
symbol  : class B
location: class A
    System.out.println(A.B.class.getName());
                        ^
Main.java:5: cannot find symbol
symbol  : class A$B
location: class Main
    System.out.println(A$B.class.getName());
                       ^
3 errors

Если я удалю файл A.java и System.out.println(A.B.class.getName()); из Main.java, все скомпилирует:

mac-grek:javajunk grek$ cat A\$B.java 
class A$B {}
mac-grek:javajunk grek$ cat Main.java 
public class Main {

  public static void main(String[] args) {
    System.out.println(A$B.class.getName());
  }

}
mac-grek:javajunk grek$ javac A\$B.java Main.java
mac-grek:javajunk grek$ 

Итак, Java позволяет мне определить класс, содержащий знак доллара в нем. Как я могу скомпилировать мой оригинальный пример?

Ответ 1

У вас конфликт имен, так как вы определили класс A $B верхнего уровня с тем же именем, что и сгенерированное имя для статического внутреннего класса B класса A. Поскольку у вас есть оба, компилятор не может разрешить конфликт,

JLS говорит:

Символ $должен использоваться только в механически сгенерированном исходном коде или, реже, для доступа к уже существующим именам в устаревших системах.

Поскольку вы решили не соблюдать это правило, вас укусил джавак. Я бы просто переименовал A $B в нечто другое.

Ответ 2

Это правило очень неопределенно.

Я не согласен. Для меня это говорит: "Не делай этого... если не знаешь, что делаешь". Он не говорит, почему, но это не нужно. В самом деле, он не может полностью объяснить, почему, поскольку некоторые из использования '$' в идентификаторах могут поступать от стороннего программного обеспечения.

Почему javac позаботится о том, что мой код поступает от какого-то генератора или написан вручную?

Он не "заботится". Но, с другой стороны, он предполагает, что вы знаете, что делаете, если вы решите игнорировать совет JLS.

Дело в том, что люди, которые пишут генераторы, должны знать, что имена двоичных классов для внутренних классов представлены с использованием символа '$'. Другие люди должны просто следовать рекомендациям в JLS.

Причина того, что спецификация языка Java не определяет, как JVM использует '$', является "разделение проблем". С точки зрения JLS это просто деталь реализации. Действительно, вполне возможно, что кто-то реализует язык Java для платформы виртуальных машин, которая по-разному относится к внутренним классам.


Я хотел бы точно знать, в каких случаях я могу использовать $в именах моих классов.

Это невозможно ответить окончательно. И, вероятно, хорошо, что это невозможно; см. ниже.

JLS должен быть очень точным и формальным и не должен ссылаться на психическое состояние читателя при интерпретации его утверждений.

  • Есть несколько областей, где было бы плохо, если бы JLS был формальным и точным. Это одна из них. Например, если бы они заявили, что "$" может быть безопасным, если следовать правилам X, Y и Z, это ограничивает их возможное использование "$" в будущих версиях Java. Изменение правил о том, какие идентификаторы работоспособны, может привести к серьезной совместимости с исходным кодом.

    (Другие области, где применяется этот принцип, - это модель памяти и семантика сбора мусора.)

  • JLS не относится к вашему психическому состоянию. Это были мои слова.

Ответ 3

Оба класса class A$B и ваш static class B разделяют то же самое. Компилятор генерирует OuterClassName$InnerClassName как имя класса для вложенных классов