12

If I have a class com.example.test.Enum2.Test as in the code below, why does getCanonicalName() return com.example.test.Enum2.Test but Class.forName() requires "com.example.test.Enum2$Test" as an argument?

Is there a way to be consistent, so that I can serialize/deserialize an enum value by its name, without having to check each $ vs . possibility, when the enum is a nested class?

package com.example.test;

import java.util.Arrays;

public class Enum2 {

    enum Test {
        FOO, BAR, BAZ;
    }

    public static void main(String[] args) {
        for (String className : Arrays.asList(
                "com.example.test.Enum2.Test",
                "com.example.test.Enum2$Test"))
        {
            try {
                Class<?> cl = Class.forName(className);
                System.out.println(className+" found: "+cl.getCanonicalName());
            }
            catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        System.out.println(Test.FOO.getDeclaringClass().getCanonicalName());
    }
}

clarification: I'm looking for a good way to deal with this problem in a real application (not just the above contrived test case), either:

a. serialize/deserialize using getCanonicalName()'s output (dotted name only), and for Class.forName() try each possibility in turn e.g. first "com.example.test.Enum2.Test", then "com.example.test.Enum2$Test", then "com.example.test$Enum2$Test", etc.

b. use proper $ notation, so that Class.forName() works right the first time. But this requires me to implement an alternative to getCanonicalName() that produces a string that is consistent with Class.forName().

I am leaning toward approach (b), partially from gut feel, and partially because approach (a) has ambiguities if there are package names with capital letters: com.example.Test.Enum2 and com.example.Test$Enum2 can both be valid inputs to Class.forName() if there is a com/example/Test/Enum2.java, and a com/example/Test.java containing an Enum2 inner class.

...but I don't know how to implement it. Any ideas?

Jason S
  • 184,598
  • 164
  • 608
  • 970

2 Answers2

10

ARGH: I should have been simply using Class.getName() instead of Class.getCanonicalName().

Jason S
  • 184,598
  • 164
  • 608
  • 970
5

Well, the docs for getCanonicalName state:

Returns the canonical name of the underlying class as defined by the Java Language Specification.

(Emphasis mine.)

In Java the language, nesting is indicated with a dot. As far as libraries and the VM are concerned, the nested class is just a class with a $ in the name.

You haven't said what kind of serialization you're talking about - so long as you're consistent for both directions, it should be fine.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • bummer. so the language's name != the VM's name. :-( Is there an easier way to convert the dotted notation to the proper $, without having to try each possibility in turn? (e.g. com.example.test.Enum2.Test, com.example.test.Enum2$Test, com.example.test$Enum2$Test, etc.) – Jason S Feb 09 '11 at 18:49
  • @Jason S: No, unfortunately it's ambiguous. xyz.A.B.C could mean `xyz.A.B.C`, or `xyz.A.B$C`, or `xyz.A$B$C` or `xyz$A$B$C`. The "dollar version" is unambiguous though. – Jon Skeet Feb 09 '11 at 19:02
  • You could make a good guess if you use the recommended naming patterns: Classes starting with a capital letter, packages all lower case. – Jorn Feb 09 '11 at 19:41