我注意到enums在编译膨胀总大小之后引入了许多额外的类文件(Class $ 1).它似乎附属于甚至使用枚举的每个类,并且这些通常是重复的.
为什么会发生这种情况,并且有一种方法可以在不删除枚举的情况下阻止这种情况
(问题的原因是空间对我来说是非常宝贵的)
编辑
在进一步研究这个问题时,每次在Enum上使用开关时,Sun的Javac 1.6都会创建一个额外的合成类.它使用某种SwitchMap.这个网站有更多信息,这里告诉你如何分析Javac正在做什么.
每次在枚举上使用开关时,额外的物理文件似乎都要付出高昂的代价!
有趣的是,Eclipe的编译器不会生成这些附加文件.我想知道唯一的解决方案是切换编译器吗?
Joh*_*ica 59
我只是被这种行为所困扰,谷歌搜索出现了这个问题.我以为我会分享一些我发现的额外信息.
每次在枚举上使用开关时,javac 1.5和1.6都会创建一个额外的合成类.该类包含一个所谓的"切换映射",它将枚举索引映射到切换表跳转号.重要的是,合成类是为发生切换的类而不是枚举类创建的.
以下是生成内容的示例:
public enum EnumClass { VALUE1, VALUE2, VALUE3 }
Run Code Online (Sandbox Code Playgroud)
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";
}
}
}
Run Code Online (Sandbox Code Playgroud)
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;
};
}
Run Code Online (Sandbox Code Playgroud)
然后,此开关映射用于生成lookupswitch或tableswitchJVM指令的索引.它将每个枚举值转换为从1到[切换个案数]的相应索引.
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
Run Code Online (Sandbox Code Playgroud)
tableswitch如果有三个或更多开关情况,则使用它,因为它执行更有效的恒定时间查找与lookupswitch线性搜索.从技术上讲,javac在使用合成开关地图时可以省略整个业务lookupswitch.
推测:我手边没有Eclipse的编译器来测试,但我想它不会打扰合成类而只是使用lookupswitch.或者它可能需要更多的开关盒,而不是原来的问题者在"ugprades"之前测试过tableswitch.
当您使用Java的枚举的"每实例方法实现"功能时,会出现$ 1等文件,如下所示:
public enum Foo{
YEA{
public void foo(){ return true };
},
NAY{
public void foo(){ return false };
};
public abstract boolean foo();
}
Run Code Online (Sandbox Code Playgroud)
上面将创建三个类文件,一个用于基本枚举类,另一个用于YEA和NAY以保存foo()的不同实现.
在字节码级别,枚举只是类,并且为了使每个枚举实例以不同方式实现方法,每个实例需要有不同的类,
但是,这并没有考虑为枚举用户生成的其他类文件,我怀疑这些只是匿名类的结果,与枚举无关.
因此,为了避免生成这样的额外类文件,请不要使用每实例方法实现.在上面的方法返回常量的情况下,您可以在构造函数中使用公共final字段集(或者如果您愿意,可以使用带有公共getter的私有字段).如果你真的需要为不同枚举实例使用不同逻辑的方法,那么你无法避免额外的类,但我认为它是一个相当奇特且很少需要的功能.
我相信这样做是为了防止开关在枚举顺序更改时中断,而不用开关重新编译类。考虑以下情况:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果没有切换图,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;
}
}
Run Code Online (Sandbox Code Playgroud)
由于case语句必须是编译时常量(例如,不是方法调用)。在这种情况下,如果A切换了的顺序,foo()则将两个打印为“ One”,反之亦然。