Java 枚举和附加类文件

发布于 2024-08-12 18:27:27 字数 615 浏览 3 评论 0原文

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

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

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

编辑

在进一步调查该问题时,Sun 的 Javac 1.6 会在每次您在 Enum 上使用开关时创建一个额外的合成类。它使用某种 SwitchMap。 网站提供了更多信息,并且 这里告诉你如何分析Javac正在做什么。

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

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

I've noticed enums introduce many additional class files (Class$1) after compilation bloating the total size. It seems to be attached to every class that even uses an enum, and these are often duplicated.

Why does this occur and is there a way to prevent this without removing the enum.

(Reason for question is space is at a premium for me)

EDIT

On investigating the issue further, Sun's Javac 1.6 creates an additional synthetic class each time you use a switch on an Enum. It uses some kind of SwitchMap. This site has some more information, and here tells you how to analyse what Javac is doing.

An additional physical file seems a high price to pay each time you use a switch on an enum!

Interestingly, Eclipe's compiler does not produce these additional files. I wonder if the only solution is to switch compilers?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

讽刺将军 2024-08-19 18:27:27

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

每次在枚举上使用开关时,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 到 [switch case 数量] 的相应索引。

E​​numUser.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

如果存在三个或更多 switch 情况,则使用 tableswitch,因为它执行比 lookupswitch 的线性搜索更有效的恒定时间查找。从技术上讲,当 javac 使用 lookupswitch 时,它可以忽略合成开关映射的整个业务。

猜测:我手头上没有 Eclipse 编译器可供测试,但我想它不会为合成类而烦恼,而只是使用 lookupswitch。或者,在“升级”为 tableswitch 之前,它可能需要比测试的原始询问者更多的 switch 情况。

I was just bit by this behavior and this question showed up when Googling. I thought I'd share the little bit of extra information I found out.

javac 1.5 and 1.6 create an additional synthetic class each time you use a switch on an enum. The class contains a so-called "switch map" which maps enum indices to switch table jump numbers. Importantly, the synthetic class is created for the class in which the switch occurs, not the enum class.

Here's an example of what gets generated:

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

This switch map is then used to generate an index for a lookupswitch or tableswitch JVM instruction. It converts each enum value into a corresponding index from 1 to [number of switch cases].

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 is used if there are three or more switch cases as it performs a more efficient constant-time lookup vs. lookupswitch's linear search. Technically speaking javac could omit this whole business with the synthetic switch map when it uses lookupswitch.

Speculation: I don't have Eclipse's compiler on hand to test with but I imagine that it doesn't bother with a synthetic class and simply uses lookupswitch. Or perhaps it requires more switch cases than the original asker tested with before it "ugprades" to tableswitch.

旧故 2024-08-19 18:27:27

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

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

如果没有 switch 映射,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”为 TWO,反之亦然。

I believe this is done to prevent switches from breaking if the ordering of the enum is changed, while not recompiling the class with the switch. Consider the following case:

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

Without the switch map, foo() would roughly translate to:

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

Since case statements must be compile-time constants (e.g. not method calls). In this case, if the ordering of A is switched, foo() would print out "One" for TWO, and vice versa.

雨轻弹 2024-08-19 18:27:27

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

public enum Foo{
    YEA{
        public void foo(){ return true };
    },
    NAY{
        public void foo(){ return false };
    };

    public abstract boolean foo();
}

上面将创建三个类文件,一个用于基本枚举类,每个用于 YEA 和 NAY 来保存不同的枚举类。 foo() 的实现。

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

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

因此,为了避免生成此类额外的类文件,请不要使用每个实例的方法实现。在上述方法返回常量的情况下,您可以使用构造函数中设置的公共最终字段(如果您愿意,也可以使用带有公共 getter 的私有字段)。如果您确实需要针对不同枚举实例具有不同逻辑的方法,那么您无法避免额外的类,但我认为它是一个相当奇特且很少需要的功能。

The $1 etc. files occur when you use the "per-instance method implementation" feature of Java's enums, like this:

public enum Foo{
    YEA{
        public void foo(){ return true };
    },
    NAY{
        public void foo(){ return false };
    };

    public abstract boolean foo();
}

The above will create three class files, one for the base enum class and one each for YEA and NAY to hold the different implementations of foo().

On the bytecode level, enums are just classes, and in order for each enum instance to implement a method differently, there needs to be a different class for each instance,

However, this does not account for additional class files generated for users of the enum, and I suspect those are just the result of anonymous classes and have nothing to do with enums.

Thus, in order to avoid such extra class files to be generated, do not use per-instance method implementations. In cases such as above where the methods return constants, you can use a public final field set in a constructor instead (or a private field with a public getter if you prefer). If you really need methods with different logic for different enum instances, then you can't avoid the extra classes, but I'd consider it a rather exotic and rarely needed feature.

酷炫老祖宗 2024-08-19 18:27:27

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

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

除了不使用枚举之外,没有办法解决这个问题。

如果空间有限,您始终可以只使用常量。

In Java, Enumerations are really just Classes with some syntactic sugar thrown on.

So anytime you define a new Enumeration, the Java compiler will create a corresponding Class file for you. (No matter how simple the Enumeration is).

No way to get around this, other then not using Enumerations.

If space is a premium, you can always just use Constants instead.

听不够的曲调 2024-08-19 18:27:27

考虑到并非所有 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 语句的工作原理。

Considering this behaviour of Java is not known to all Java developers, I created few videos explaining how Switch statements in Java works.

  1. Switch with Enums - https://www.youtube.com/watch?v=HlsPHEB_xz4
  2. Switch with Strings - https://www.youtube.com/watch?v=cg9O815FeWY
  3. About TableSwitch and LookupSwitch - https://www.youtube.com/watch?v=OHwDczHbPcw
  4. Switch Expression in Java 13 - https://www.youtube.com/watch?v=suFn87Irpb4

This might not answer the question in a straight way. However, it does definitely answers how the switch statements in Java works.

瑾夏年华 2024-08-19 18:27:27

据我所知,给定一个名为 Operation 的枚举,您将获得额外的类文件,不包括明显的 Operation.class,并且每个枚举值一个,如果您使用 抽象方法就像这样:

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

as far I know, given an enum named Operation you will get additional class files, excluding the obvious Operation.class, and one per enum value, if you are using abstract method like this one:

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文