4

As of Java 16, we can now define an enum locally, within a method. This feature came as part of the work for the new records feature. (Interfaces too can be local, by the way.)

What is the fully-qualified name of such a locally-defined enum class?

The following code works if I were define the enum in a separate .java file. How should I change this fully-qualified class name for a local enum?

package work.basil.example.enums;

public class EnumMagic
{
    public static void main ( String[] args )
    {
        EnumMagic app = new EnumMagic ( );
        app.demo ( );
    }

    private void demo ( )
    {
        enum Shape { CIRCLE, SQUARE }
        try
        {
            Class < ? > clazz = Class.forName ( "work.basil.example.enums.Shape" );
            System.out.println ( "clazz = " + clazz );
        }
        catch ( ClassNotFoundException e ) { throw new RuntimeException ( e ); }
    }
}

As currently written, I understandably get an java.lang.ClassNotFoundException: work.basil.example.enums.Shape.

Neither of these work:

  • "work.basil.example.enums.EnumMagic.Shape"
  • "work.basil.example.enums.EnumMagic.$Shape"
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154

2 Answers2

2

Local classes, enums and interfaces don't have a qualified name. Just a simple name. In your case: Shape

From: https://docs.oracle.com/javase/specs/jls/se15/preview/specs/local-statics-jls.html#jls-14.3

Mar-Z
  • 2,660
  • 2
  • 4
  • 16
  • I see that `Class> clazz = Class.forName(Shape.class.getName());` can be used - and it prints `clazz = class com.my.package.EnumMagic$1Shape`. I don't know what the `1` after the `$` signifies. – andrewJames May 31 '23 at 20:15
  • 2
    @andrewJames It lets compiler (and us) differentiate between many *local* classes with same name. For instance *in same method* we can have separate code blocks with their own local classes but with same name like `{class ABC{} System.out.println("first class: "+ABC.class);} {class ABC{} System.out.println("second class: "+ABC.class);}`. As you see those *separate* classes have same name `ABC`, so compiler needs some way to differentiate them and `$1ABC` and `$2ABC` does it quite nicely. – Pshemo May 31 '23 at 20:24
  • Thank you @Pshemo. Does this mean our locally defined enum _does_ have a qualified name? It seems to, as far as `Class.forName` is concerned (I'm using Java 19). – andrewJames May 31 '23 at 20:27
  • @andrewJames Yes. In OP case `work.basil.example.enums.EnumMagic$1Shape` – Pshemo May 31 '23 at 20:30
  • So what exactly is a "simple name"? I cannot decipher that Java Language Spec. I do find that `Class < ? > clazz = Class.forName ( "Shape" );` fails while `Class < ? > clazz = Class.forName ( "work.basil.example.enums.EnumMagic$1Shape" );` succeeds. Isn't `work.basil.example.enums.EnumMagic$1Shape` a fully-qualified name rather than a simple name? – Basil Bourque Jun 01 '23 at 00:26
  • 3
    @andrewJames [`Class.getName()`](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Class.html#getName()) returns the [*Binary Name*](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/ClassLoader.html#binary-name) of the class. That’s not the qualified name. – Holger Jun 01 '23 at 07:56
  • Thank you @holger for that clarification. This led me to research questions such as [What is the difference between canonical name, simple name and class name in Java Class?](https://stackoverflow.com/q/15202997/12567365) - including [this answer](https://stackoverflow.com/a/36978943/12567365). – andrewJames Jun 01 '23 at 12:33
1

I gleaned the following info from the comments on the Answer by Mar-Z.

Class#getName

You can discover the name of the class in question by interrogating through Java code. Call Class#getName, like this:

Shape.class.getName()

Example:

System.out.println ( Shape.class.getName() );  

work.basil.example.enums.EnumMagic$1Shape

So we can plug that into the Question’s code.

enum Shape { CIRCLE, SQUARE }
try
{
    Class < ? > clazz = Class.forName ( "work.basil.example.enums.EnumMagic$1Shape" );
    System.out.println ( "clazz = " + clazz );
}
catch ( ClassNotFoundException e ) { throw new RuntimeException ( e ); }

clazz = class work.basil.example.enums.EnumMagic$1Shape

Caveat: Unfortunately, this hard-coded class name string is fragile. The name could change in later builds of your code. See discussion in Comments below.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 6
    Ah, the good old “*I have discovered that this happened to work, let’s build our application on the assumption that it will always work*”… – Holger Jun 01 '23 at 08:00
  • @Holger Your comment escapes me. Can you explain? What is it here that “happens to work” and what might cease to work? – Basil Bourque Jun 01 '23 at 14:12
  • 2
    Holger is (rightfully) criticising you for depending on the assumption that `Class#getName()` will give you the name necessary to extract a `Class` from a `String` when using `Class#forName(String)`. For example, $1 is decided based on how many other local classes that exist with the same name. If you save that $1 name, and then later, you recompile this code so that that same class now has a $2 name, then your code will break in scary ways. In short, you are basing this answer on some shaky assumptions. – davidalayachew Jun 01 '23 at 14:28
  • 1
    Perhaps your solution can afford to make that assumption, and therefore, this solution is fine for your purposes (you will never recompile again and there is no chance for dynamic linkage problems), but it still paints a deceptive picture to those looking in from the outside. It makes it sound like `Class#forName(String)` and `Class#getName` are safe ways to go from a `String` to a `Class` and back, when in reality, it is a ***VERY*** shaky bridge – davidalayachew Jun 01 '23 at 14:30
  • 1
    There’s no guaranty that the name will be `work.basil.example.enums.EnumMagic$1Shape`. As davidalayachew said, the `$1` might change depending on the presence of other classes but even that is compiler-specific. The next compiler (version) may use the total count of all generated classes or just start counting at zero instead of one. But I can’t imagine any use case for calling `Class.forName` with a hard-coded string for a class declared within the same method anyway… – Holger Jun 01 '23 at 14:34
  • To your credit Basil Bourque, the contract for [`Class#forName`](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Class.html#forName(java.lang.String,boolean,java.lang.ClassLoader)) even agrees with you, so I am not saying that it was bad logic to make that assumption (frankly, I'd say the JavaDoc is misleading). I am saying that is wrong because I have made this mistake many times before. Here is a similar but slightly different example of just how shaky this bridge actually is. https://bugs.openjdk.org/browse/JDK-8287885 – davidalayachew Jun 01 '23 at 14:44
  • @BasilBourque and just to explain the link a bit, the code failed to find the class because the way Windows handles case and Java handles case is different. And yet, this discrepancy exists still today. The solution they came up for with this bug was to add a compiler warning so that you could only generate classes that align with both Windows and Java's idea of case. While that solution works, even the guy himself who implemented said it was a best-effort solution to an ugly problem. Here is the relevant discussion on it. https://github.com/openjdk/jdk/pull/12754#issuecomment-1517868153 – davidalayachew Jun 01 '23 at 14:50
  • 3
    @davidalayachew you can pass the result of `Class.getName()` to `Class.forName(…)` within one runtime, but you can’t convert the name into a hard-coded string literal and expect it to work in the future. – Holger Jun 01 '23 at 15:08
  • @Holger exactly correct. And that was more my point -- according to the JavaDoc, this method is really only safe for the duration of the runtime. Once that ends, you should freshly generate the `String` from the `Class` before reattempting. But the JavaDoc was not clear about that at all. I only knew that because how many times I bumped my head on this problem. I would submit a PR or a bug submission if I wasn't currently swamped with work. Since it is a simple JavaDoc change, the jdk folks involved would likely be receptive to it. – davidalayachew Jun 01 '23 at 15:12