Java枚举和其他类文件


71

我注意到enums在编译使整体大小膨胀之后,引入了许多其他类文件(Class $ 1)。它似乎附加在每个甚至使用枚举的类上,并且它们经常重复。

为什么会发生这种情况,并且有办法在不删除枚举的情况下防止这种情况发生。

(问题的原因是空间对我来说是非常宝贵的)

编辑

在进一步调查问题时,每次使用Enum上的开关时,Sun的Javac 1.6都会创建一个附加的综合类。它使用某种SwitchMap。站点提供了更多信息,在这里告诉您如何分析Javac在做什么。

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

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


Class $ n类文件是匿名内部类。我大量使用了枚举,却没有看到。您可以发布执行此操作的源文件吗?
Devon_C_Miller

您担心哪种“膨胀”?希望没有磁盘空间。:)如果是下载大小,我是否可以推荐.pack200.gz(前几天我天真地将CORBA包装为实验-最著名的Java膨胀不到48K)。
Tom Hawtin-抢险活动

是的,它是小程序打包的Jar文件的下载大小。我正在尝试采取步骤在可能的情况下进行压缩(不进行任何源代码级别更改)。pack200可以在这种情况下工作吗?

Answers:


63

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

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

这是生成的示例:

枚举类

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

然后,此开关映射用于为lookupswitchtableswitchJVM指令生成索引。它将每个枚举值转换为从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


轶事:今天我遇到了这种情况(特别是Eclipse的[JDK 8]编译器没有这样做),并伴随着以下行为:coderanch.com/t/457290/Testing/Test-class-public-constructor-这意味着在本地运行的测试在Jenkins上有些莫名其妙地失败了,因为Jenkins的配置不够严格,并且编译器的行为与本地的不同。由于我们无法完全控制自己的构建,因此我们可能不得不暂时避免在单元测试中切换枚举。
里克·莫里茨

8

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

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


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()的不同实现。

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

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

因此,为了避免生成此类额外的类文件,请不要使用按实例的方法实现。在诸如上述方法返回常量的情况下,您可以改用构造函数中的public final字段集(如果愿意,也可以使用带有public getter的私有字段)。如果您确实需要针对不同的枚举实例使用不同逻辑的方法,那么您就无法避免使用多余的类,但是我认为这是一个非常奇特且很少需要的功能。


嗨,谢谢您的回答,但在这种情况下并非如此(请参见上面的编辑)。
游泳池

非常有趣-我想在这种情况下,有必要将您的发现作为单独的答案发布并接受。
Michael Borgwardt

1

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

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

除了没有使用Enumerations,其他方法都无法解决。

如果空间有限,那么您始终可以使用常量来代替。


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语句如何工作。


-1

据我所知,给定一个名为enum的枚举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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.