Java class name containing dollar sign fails to compile if an inner class is present

I've got following Java classes defined:

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());
  }

}

When I try to compile them, I get following errors:

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

If I remove A.java file and System.out.println(A.B.class.getName()); from Main.java everything compiles:

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$ 

So Java allows me to define a class containing dollar sign in it's name. How can I compile my original example?


Solution 1:

You have a name conflict because you defined a top-level class A$B having the same name as the generated name for a static inner class B of class A. Since you have both, the compiler can't resolve the conflict.

The JLS says:

The $ character should be used only in mechanically generated source code or, rarely, to access pre-existing names on legacy systems.

Since you decided not to respect that rule, you got bitten by javac. I would just rename A$B to something else.

Solution 2:

This rule is very vague.

I disagree. To me, it says "don't do it ... unless you know what you are doing". It doesn't say why, but it doesn't need to. Indeed, it can't fully explain why because some of the uses of '$' in identifiers could come from 3rd-party software.

Why javac would care if my code is coming from some generator or written by hand?

It doesn't "care". But on the other hand it does assume that you know what you are doing if you choose to ignore the JLS advice.

The point is that people who write generators are expected to know that the binary class names for inner classes are represented using the '$' character. Other people should simply follow the advice in the JLS.

The reason that the Java Language Specification does not set out how the JVM uses '$' is "separation of concerns". From the JLS perspective, this is just an implementation detail. Indeed, it is conceivable that someone will implement the Java language for a virtual machine platform that treats inner classes differently.


I'd like to know precisely in which cases I can use $ in names of my classes.

It is not possible to answer that definitively. And it is probably a good thing that it is not possible; see below.

JLS should be very precise and formal and should not refer to reader's mental state when interpreting it's statements.

  1. There are some areas where it would be bad for the JLS to be formal and precise. This is one of them. For example, if they declared that '$' could safely if you followed rules X, Y and Z, this would limit their possible uses of '$' in future versions of Java. Changing the rules about what identifiers are workable could cause major source-code compatibility head-aches.

    (Other areas where this principle applies are the Memory Model and the semantics of garbage collection.)

  2. The JLS does not refer to your mental state. Those were my words.

Solution 3:

Both your class class A$B and your static class B shares the same. Compiler generates OuterClassName$InnerClassName as class name for nested classes