如何将已编译的类名与Java中的枚举成员进行匹配?

时间:2010-10-22 16:23:53

标签: java enums compilation

在Java中,使用Sun的JDK 1.6,使用这样的枚举:

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

编译的文件是:

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

这也意味着显示foo()的堆栈跟踪或JVisualVM中打印的方法调用等将在顶部显示如下内容:

mypackage.MyEnum$1.run()

类名中的$1是由于枚举的成员被编译为匿名内部类。我想知道是否可以安全地假设这些类名中使用的数字映射到枚举成员的定义顺序?如果不是,是否有一种标准的保证方法可以从用作匿名类名的数字中找到枚举成员?


修改

关于枚举的设计,这仅用于说明目的。真正的枚举实现了一个接口,每个成员提供了不同的方法实现。请不要过分关注那些看起来有点奇怪的东西。


编辑#2

为了澄清,我不是试图用这些信息以编程方式(例如奇怪的反射废话)做任何事情。相反,我正在查看堆栈跟踪和分析信息,并尝试将枚举成员(显示为匿名类上的调用)上的方法调用映射到源代码中的实际枚举成员。

5 个答案:

答案 0 :(得分:3)

在stacktraces中,您还将获得源文件中的行号。假设你有源,那将揭示它是什么常数。 (在eclipse中,只需单击控制台视图中的行号即可直接导航到源代码。)

要查找类的常量名称,可以获取枚举的类文件,并反汇编静态初始化程序。例如,如果你编译:

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

然后执行:

javap -c PieceColor

你得到:

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;

但是可能有更优雅的方法,但如果其他所有方法都失败了,这应该可以解决问题。

答案 1 :(得分:1)

您在尝试使用匿名类名称是什么?如果您提供更多详细信息,我们可能会提供更好的解决方案。

我猜班级的名字等于ordinal() + 1。但是,这是一个实现细节,可以在引入或删除枚举成员时更改,因此不建议依赖它。

有效的Java第2版,第31项详细解释了为什么使用实例字段而不是序数更好。

答案 2 :(得分:1)

您可以与MyEnum.FIRST_MEMBER.getClass().getName()进行比较,它会为您提供为匿名类生成的名称。

匿名类的命名顺序可能是一致的,但不保证,所以我不建议依赖它。

您也可以使用非匿名类,在这种情况下,您将知道每个类的名称。

如果您被迫使用匿名类并且不想在代码中执行此操作,我相信您只需要尝试手动跟踪。

但是您可以将上面的代码用作调试工具,方法是将其作为辅助方法或调试器运行。这样您就可以确认哪个匿名类正在接收每个名称。

答案 3 :(得分:0)

一般来说,使用ordinal()做很多事情都是个坏主意,因为它可以很容易地改变。

如果由于某种原因你有类名并希望找到相应的枚举值,那么在我看来最简单的方法是对类名进行Class.forName(),然后循环遍历枚举成员(使用静态values()方法)并在每个方法上调用getClass()并查看它是否等于您的Class对象。

我没有测试上面的内容,但它似乎应该可行,并且可靠。

答案 4 :(得分:0)

首先,我不相信编译器如何为匿名内部类选择名称有任何保证。话虽这么说,如果这是你不希望经常做的一次性分析,那就写一个简单的测试来看看这些名字是否与你期望的一致,我的猜测就是他们这样做了。如果您希望在新版本的编译器发布之前完成分析,那么不要过于担心它。

如果您正在寻找一个更长期的解决方案,而您需要的只是对日志和分析数据的静态分析,为什么不让您的系统在启动时记录每个枚举类型的类名?所以在任何bootstrap钩子里面你都做了类似的事情:

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