首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Java枚举和其他类文件

Java枚举和其他类文件
EN

Stack Overflow用户
提问于 2009-12-03 01:49:25
回答 6查看 15.1K关注 0票数 71

我注意到enums在编译后引入了许多额外的类文件( class $1),使总大小膨胀。它似乎附加到甚至使用枚举的每个类,并且这些类通常是重复的。

为什么会发生这种情况,有没有一种方法可以在不删除枚举的情况下防止这种情况。

(提出问题的原因是空间对我来说很重要)

编辑

在进一步研究这个问题时,Sun Javac1.6在每次您在上使用开关时都会创建一个额外的合成类。它使用了某种SwitchMap。This站点有更多信息,here告诉您如何分析Javac正在做什么。

每次在枚举上使用开关时,额外的物理文件似乎都要付出很高的代价!

有趣的是,Eclipe的编译器不会生成这些额外的文件。我想知道唯一的解决方案是不是换个编译器?

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2010-12-01 06:04:23

我只是被这种行为所困扰,当我在谷歌上搜索时,这个问题出现了。我想我应该分享我发现的一点额外信息。

每次在枚举上使用开关时,javac 1.5和1.6都会创建一个额外的合成类。该类包含一个所谓的"switch map“,它将枚举索引映射到交换表跳转编号。重要的是,合成类是为发生切换的类创建的,而不是枚举类。

下面是一个生成内容的示例:

EnumClass.java

代码语言:javascript
复制
public enum EnumClass { VALUE1, VALUE2, VALUE3 }

EnumUser.java

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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的线性搜索。从技术上讲,当它使用lookupswitch时,它可以通过合成切换映射省略整个业务。

猜测:我手头没有可以测试的Eclipse编译器,但我想它不会使用合成类,而是简单地使用lookupswitch。或者,它可能需要比测试的原始请求者更多的切换用例,然后才能将其"ugprades“到tableswitch

票数 64
EN

Stack Overflow用户

发布于 2012-04-25 02:43:03

我相信这样做是为了防止开关在枚举的顺序改变时中断,同时不会使用开关重新编译类。考虑以下情况:

代码语言:javascript
复制
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()将大致转换为:

代码语言:javascript
复制
 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()将打印“1”作为2,反之亦然。

票数 8
EN

Stack Overflow用户

发布于 2009-12-03 02:16:16

当您使用Java枚举的“每个实例的方法实现”特性时,会出现$1等文件,如下所示:

代码语言:javascript
复制
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实例使用具有不同逻辑的方法,那么您无法避免额外的类,但我认为这是一个非常奇特且很少需要的特性。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1834632

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档