Java枚举和其他类文件
我注意到Java枚举和其他类文件,java,enums,Java,Enums,我注意到enum在编译后引入了许多额外的类文件(class$1),使总大小膨胀。它似乎附加到每一个甚至使用枚举的类,并且这些类通常是重复的 为什么会发生这种情况?有没有一种方法可以在不删除枚举的情况下防止这种情况发生 (问题的原因是空间对我来说是额外的) 编辑 在进一步研究这个问题时,Sun的JavaC1.6在每次使用枚举开关时都会创建一个额外的合成类。它使用某种开关映射。该站点提供了更多信息,并告诉您如何分析Javac正在做什么 每次在枚举上使用开关时,额外的物理文件似乎要付出高昂的代价 有趣
enum
在编译后引入了许多额外的类文件(class$1),使总大小膨胀。它似乎附加到每一个甚至使用枚举的类,并且这些类通常是重复的
为什么会发生这种情况?有没有一种方法可以在不删除枚举的情况下防止这种情况发生
(问题的原因是空间对我来说是额外的)
编辑
在进一步研究这个问题时,Sun的JavaC1.6在每次使用枚举开关时都会创建一个额外的合成类。它使用某种开关映射。该站点提供了更多信息,并告诉您如何分析Javac正在做什么
每次在枚举上使用开关时,额外的物理文件似乎要付出高昂的代价
有趣的是,Eclipe的编译器不会生成这些附加文件。我想知道唯一的解决方案是切换编译器吗?在Java中,枚举实际上只是一些添加了语法糖分的类 因此,无论何时定义新的枚举,Java编译器都会为您创建相应的类文件。(无论枚举多么简单) 除了不使用枚举之外,没有办法解决这个问题
如果空间很重要,您可以始终使用常量来代替。当您使用Java枚举的“每个实例方法实现”功能时,会出现$1等文件,如下所示:
public enum Foo{
YEA{
public void foo(){ return true };
},
NAY{
public void foo(){ return false };
};
public abstract boolean foo();
}
上面将创建三个类文件,一个用于基本枚举类,另一个用于YEA和NAY,以保存foo()的不同实现
在字节码级别上,枚举只是类,为了让每个枚举实例以不同的方式实现方法,每个实例需要有不同的类
但是,这并没有考虑为枚举用户生成的其他类文件,我怀疑这些只是匿名类的结果,与枚举无关
因此,为了避免生成此类额外的类文件,不要使用每个实例方法实现。在上述方法返回常量的情况下,您可以使用构造函数中设置的公共final字段(如果愿意,也可以使用带有公共getter的私有字段)。如果您确实需要不同的枚举实例的不同逻辑的方法,那么您不能避免额外的类,但我认为它是一个相当异乎寻常和很少需要的特性。 到目前为止,我知道,给定一个枚举名为“代码>操作< /代码>,您将得到附加的类文件,不包括明显的<代码>操作。如果您使用的是像下面这样的
抽象方法
,则每个枚举值一个:
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);
}
我只是被这种行为咬了一口,这个问题在谷歌搜索时出现了。我想我会分享我发现的一些额外信息 每次在枚举上使用开关时,JavaC1.5和1.6都会创建一个额外的合成类。该类包含一个所谓的“开关映射”,它将枚举索引映射到开关表跳转数。重要的是,合成类是为发生切换的类而不是枚举类创建的 以下是生成内容的示例: EnumClass.java EnumUser.java 综合枚举用户$1.0类 然后使用此开关映射为
lookupswitch
或tableswitch
JVM指令生成索引。它将每个枚举值转换为从1到[切换案例数]的对应索引
枚举用户类
表开关
用于三种或三种以上的开关情况,因为它执行的恒定时间查找比查找开关
的线性搜索更有效。从技术上讲,javac在使用lookupswitch
时,可以省略合成开关映射的整个业务
推测:我手头上没有Eclipse的编译器可供测试,但我认为它不需要处理合成类,只需使用
lookupswitch
。或者,它可能需要比之前测试的原始asker更多的开关情况,即“ugprades”到表开关我相信这样做是为了防止在更改枚举顺序时开关中断,而不是使用开关重新编译类。考虑以下情况:
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开发人员都知道,我制作了一些视频来解释Java中的Switch语句是如何工作的
带枚举的开关-
带字符串的开关-
关于表开关和查找开关-
Java 13中的开关表达式-
这可能无法直接回答这个问题。但是,它确实回答了Java中switch语句的工作原理。Class$n类文件是匿名的内部类。我已经大量使用了枚举,但还没有看到这一点。你能发布一个这样做的源文件吗?你担心什么样的“膨胀”?希望不是磁盘空间。:)如果是下载大小,我可以推荐.pack200.gz
(前几天我天真地将CORBA打包作为一个实验——最著名的Java膨胀不到48K)。是的,这是小程序打包Jar文件的下载大小。我正试图采取措施尽可能地压缩它(不需要任何源代码级别的更改)。pack200对这种情况有效吗?您好,谢谢您的回答,但在本例中并非如此(请参见上面的编辑)。非常有趣-我想说,在这种情况下,有必要将您的发现作为一个单独的答案发布并接受。一则轶事:我今天遇到了这个问题(特别是Eclipse的[JDK 8]编译器没有这样做)结合此行为:--这意味着运行本地
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;
};
}
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
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;
}
}
}
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;
}
}