views:

41

answers:

5

In Java, with Sun's JDK 1.6, with an enum such as this:

public enum MyEnum {
    FIRST_MEMBER { public void foo() { } },
    SECOND_MEMBER { public void foo() { } }, 
    THIRD_MEMBER { public void foo() { } };
}

The compiled files are:

MyEnum$1.class  MyEnum$2.class  MyEnum$3.class  MyEnum.class 

This also means that a stack trace showing foo(), or the method call printed in JVisualVM, etc., will have something like this at the top:

mypackage.MyEnum$1.run()

The $1 in the class name is due to the members of the enum being compiled to anonymous inner classes. I am wondering if it is safe to assume that the numbers used in these class names map to the order in which the enum members are defined? If it is not, is there a standard, guaranteed way to find the enum member from the number used as the anonymous class name?


EDIT

In regards to the design of the enum, this was used for illustration purposes only. The real enum implements an interface, and each member provides a different implementation of the methods. Please don't pay too much attention to what admittedly looks a bit strange.


EDIT #2

To clarify, I am not trying to do anything with this information programmatically (such as weird reflection nonsense). Rather, I am looking at stack traces and profiling information, and trying to map a method call on an enum member (shown as a call on an anonymous class) against the actual enum member in the source code.

+1  A: 

What are you trying to use the anonymous class name for? If you give more details, we may be able to offer better solutions.

I guess the class' name equals to ordinal() + 1. However, this is an implementation detail, which can change as you introduce or remove enum members, so it is not recommended to rely on it.

Effetive Java 2nd Edition, Item 31 explains in details why is it better to use instance fields instead of ordinals.

Péter Török
Please see edit #2.
Grundlefleck
@Grundlefleck, my guess is that you can map between the ordinal and the class name in stack traces (within a given execution run). However, if your enums do so much that you need to analyse their method calls in the profiler, maybe you could refactor to move the heavier stuff from the enum methods into other, proper named class(es).
Péter Török
+1  A: 

You can do a comparison against MyEnum.FIRST_MEMBER.getClass().getName() which will give you the name generated for the anonymous class.

The order that the anonymous classes are named will probably be consistent, but it is not guaranteed, so I would not recommend relying on it.

You also could use non-anonymous classes, in which case you would know the name of each.

If you are forced to use anonymous classes and do not want to do it in code, I believe you will just need to try to keep track manually.

But you could use my above code as a debugging tool by running it as a helper method or in a debugger. That way you can confirm which anonymous class is receiving each name.

Alan Geleynse
But I don't have control over the anonymous classes, that's what the compiler is generating from the enum definition. Also, please note that I only want to map between the two manually, not in code - I'm not that crazy ;-)
Grundlefleck
I added a little more detail in my answer. Unfortunately I think you will have to use the code either in a helper method or a debugger. I don't think there is any other way to guarantee the ordering.
Alan Geleynse
A: 

In general doing much of anything with ordinal() is a bad idea because it can change so easily.

If you have the class name for some reason and want to find the corresponding enum value, the easiest seems to me to be to do a Class.forName() on the class name, and then loop through the enum members (using the static values() method) and call getClass() on each and see if it equals your Class object.

I haven't test the above, but it seems it should work, and be reliable.

Yishai
A: 

In stacktraces, you'll also get the line number in the source file. Assuming you have the source, that'll reveal which constant it is. (In eclipse, simply click the line number in the console view to directly navigate to the source).

To find the constant name for a class, you could grab the class file for the enum, and disassemble the static initializer. For instance, if you compile:

enum PieceColor {
    Black {
        @Override public String toString() { return "dark";}
    },
    White {
        @Override public String toString() { return "light";}       
    }
}

and then do:

javap -c PieceColor

you get:

static {};
  Code:
   0:   new #13; //class tools/PieceColor$1
   3:   dup
   4:   ldc #15; //String Black
   6:   iconst_0
   7:   invokespecial   #16; //Method tools/PieceColor$1."<init>":(Ljava/lang/String;I)V
   10:  putstatic   #20; //Field Black:Ltools/PieceColor;
   13:  new #22; //class tools/PieceColor$2
   16:  dup
   17:  ldc #24; //String White
   19:  iconst_1
   20:  invokespecial   #25; //Method tools/PieceColor$2."<init>":(Ljava/lang/String;I)V
   23:  putstatic   #26; //Field White:Ltools/PieceColor;

But there might be a more elegant method, but if all else fails, this should do the trick.

meriton
A: 

First, I don't believe there is any guarantee about how the compiler chooses names for anonymous inner classes. That being said, if this is a one-time type of analysis that you don't expect to do often, write a simple test to see if the names match up they way you expect, my guess is that they do. If you expect to be done with your profiling before a new version of the compiler comes out, then don't worry too much about it.

If you are looking for a bit longer term solution and all you need it for is static analysis of logs and profiling data, why not have your system log the class names for each enum type at startup? So inside whatever bootstrap hook you have do something like:

for(MyEnum value : MyEnum.values()) {
    logger.info(String.format("MyEnum.%s maps to classname %s", value.name(), value.getClass.getName()));
}
Mike Deck