Java枚举和其他类文件

我注意到enums在编译后会引入很多额外的类文件(Class $ 1) 这似乎是每个类甚至使用枚举,这些往往是重复的附加。

为什么会发生这种情况,并有一种方法来防止这种情况,而不去除枚举。

(问题的原因是空间对我来说是一个溢价)

编辑

在进一步调查问题时,Sun的Javac 1.6每次在Enum上使用开关时都会创build一个附加的合成类 。 它使用某种SwitchMap。 这个网站有更多的信息, 这里告诉你如何分析Javac正在做什么。

每次在枚举上使用开关时,额外的物理文件似乎是一个高昂的代价!

有趣的是,Eclipe的编译器不会产生这些额外的文件。 我不知道唯一的解决scheme是切换编译器吗?

我只是一点点的这种行为,这个问题出现在Google上时。 我想我会分享我发现的额外信息的一点点。

每次在枚举上使用开关时,javac 1.5和1.6都会创build一个附加的合成类。 该类包含一个所谓的“切换映射”,它将枚举索引映射到切换表跳转数字。 重要的是,合成类是为交换发生的类而不是枚举类创build的。

以下是生成内容的示例:

EnumClass.java

 public enum EnumClass { VALUE1, VALUE2, VALUE3 } 

EnumUser.java

 public class EnumUser { public String getName(EnumClass value) { switch (value) { case VALUE1: return "value 1"; // No VALUE2 case. case VALUE3: return "value 3"; default: return "other"; } } } 

合成EnumUser $ 1.class

 class EnumUser$1 { static final int[] $SwitchMap$EnumClass = new int[EnumClass.values().length]; static { $SwitchMap$EnumClass[EnumClass.VALUE1.ordinal()] = 1; $SwitchMap$EnumClass[EnumClass.VALUE3.ordinal()] = 2; }; } 

然后使用此切换映射为lookupswitchtableswitch JVM指令生成索引。 它将每个枚举值转换为一个对应的索引,从1到[切换个案数]。

EnumUser.class

 public java.lang.String getName(EnumClass); Code: 0: getstatic #2; //Field EnumUser$1.$SwitchMap$EnumClass:[I 3: aload_1 4: invokevirtual #3; //Method EnumClass.ordinal:()I 7: iaload 8: lookupswitch{ //2 1: 36; 2: 39; default: 42 } 36: ldc #4; //String value 1 38: areturn 39: ldc #5; //String value 3 41: areturn 42: ldc #6; //String other 44: areturn 

如果有三个或更多开关情况,则使用tableswitch因为它执行更有效的恒定时间查找与lookupswitch的线性search。 从技术上来说,javac在使用lookupswitch时,可以用合成切换图省略整个业务。

猜测:我手边没有Eclipse的编译器来testing,但是我想象它不会为复合类而烦恼,而只是使用lookupswitch 。 或者,也许它需要更多的开关情况下比原来的提问者testing之前,“ugprades”tableswitch。

当您使用Java枚举的“per-instance方法实现”function时,会出现$ 1等文件,如下所示:

 public enum Foo{ YEA{ public void foo(){ return true }; }, NAY{ public void foo(){ return false }; }; public abstract boolean foo(); } 

以上将创build三个类文件,一个用于基本枚举类,另一个用于YEA和NAY来保存foo()的不同实现。

在字节码级别,枚举只是类,为了让每个枚举实例不同地实现一个方法,每个实例都需要一个不同的类,

但是,这并没有考虑为枚举用户生成的额外的类文件,我怀疑这些只是匿名类的结果,并且与枚举无关。

因此,为了避免生成这样的额外的类文件,不要使用每个实例的方法实现。 在上面这些方法返回常量的情况下,您可以使用在构造函数中设置的公共final字段(如果您愿意,也可以使用具有公共getter的私有字段)。 如果你真的需要具有不同逻辑的方法来处理不同的枚举实例,那么你不能避免额外的类,但是我认为它是一个相当奇特而且很less需要的特性。

我相信这样做是为了防止如果枚举的sorting改变,而不是重新编译与交换机类的开关打破。 考虑以下情况:

 enum A{ ONE, //ordinal 0 TWO; //ordinal 1 } class B{ void foo(A a){ switch(a){ case ONE: System.out.println("One"); break; case TWO: System.out.println("Two"); break; } } } 

没有开关映射, foo()将大致转换为:

  void foo(A a){ switch(a.ordinal()){ case 0: //ONE.ordinal() System.out.println("One"); break; case 1: //TWO.ordinal() System.out.println("Two"); break; } } 

由于case语句必须是编译时常量(例如不是方法调用)。 在这种情况下,如果切换A的顺序,则foo()将打印出“一”,反之亦然。

在Java中,枚举实际上只是带有一些语法糖的类。

所以无论何时定义一个新的枚举,Java编译器都会为你创build一个对应的Class文件。 (不pipe枚举有多简单)。

没有办法解决这个问题,其他不使用枚举。

如果空间是一个溢价,你可以随时使用常量。

据我所知,给定一个名为Operation的枚举,你会得到额外的类文件,不包括显而易见的Operation.class和一个枚举值,如果你使用这样的abstract method

 enum Operation { ADD { double op(double a, double b) { return a + b; } }, SUB { double op(double a, double b) { return a - b; } }; abstract double op(double a, double b); }