Java枚举和其他类文件

时间:2009-12-02 17:49:25

标签: java enums

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

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

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

修改

在进一步调查此问题时,每次在Enum上使用开关时,Sun的 Javac 1.6都会创建一个额外的合成类。它使用某种SwitchMap。 This网站提供了更多信息,here告诉您如何分析Javac正在做的事情。

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

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

6 个答案:

答案 0 :(得分:57)

我只是被这种行为所困扰,谷歌搜索出现了这个问题。我以为我会分享一些我发现的额外信息。

每次在枚举上使用开关时,

javac 1.5和1.6都会创建一个额外的合成类。该类包含一个所谓的“切换映射”,它将枚举索引映射到切换表跳转号。重要的是,合成类是为发生切换的类创建的,不是枚举类。

以下是生成内容的示例:

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";
        }
    }
}

Synthetic 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的线性搜索。从技术上讲,javac在使用lookupswitch时可以使用合成开关地图省略整个业务。

推测:我手边没有Eclipse编译器进行测试,但我想它不会打扰合成类而只使用lookupswitch。或者它可能需要比原始提问者更多的切换案例,然后才能“ugprades”到tableswitch

答案 1 :(得分:6)

当您使用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的私有字段)。如果你真的需要为不同枚举实例使用不同逻辑的方法,那么你无法避免额外的类,但我认为它是一个相当奇特且很少需要的功能。

答案 2 :(得分:4)

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

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()将为TWO打印出“One”,反之亦然。

答案 3 :(得分:1)

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

因此,只要您定义新的Enumeration,Java编译器就会为您创建相应的Class文件。 (无论枚举多么简单)。

无法解决这个问题,除此之外不使用Enumerations。

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

答案 4 :(得分:0)

考虑到Java的这种行为并不是所有Java开发人员都知道的,我制作了一些视频来解释Java中的Switch语句如何工作。

  1. 用枚举切换-https://www.youtube.com/watch?v=HlsPHEB_xz4
  2. 使用字符串切换-https://www.youtube.com/watch?v=cg9O815FeWY
  3. 关于TableSwitch和LookupSwitch-https://www.youtube.com/watch?v=OHwDczHbPcw
  4. 在Java 13中切换表达式-https://www.youtube.com/watch?v=suFn87Irpb4

这可能无法直接回答问题。但是,它确实可以回答Java中的switch语句如何工作。

答案 5 :(得分:-1)

据我所知,给定一个名为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);
}