Java 枚举和其他类文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1834632/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 18:10:51  来源:igfitidea点击:

Java enum and additional class files

javaenums

提问by Pool

I've noticed enumsintroduce 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.

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

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. Thissite has some more information, and heretells you how to analyse what Javac is doing.

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

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?

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

采纳答案by John Kugelman

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, notthe enum class.

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

Here's an example of what gets generated:

这是生成内容的示例:

EnumClass.java

枚举类

public enum EnumClass { VALUE1, VALUE2, VALUE3 }

EnumUser.java

枚举用户.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

合成 EnumUser$1.class

class EnumUser {
    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 lookupswitchor tableswitchJVM instruction. It converts each enum value into a corresponding index from 1 to [number of switch cases].

然后使用此开关映射为lookupswitchtableswitchJVM 指令生成索引。它将每个枚举值转换为从 1 到 [开关案例数] 的相应索引。

EnumUser.class

枚举用户类

public java.lang.String getName(EnumClass);
  Code:
   0:   getstatic       #2; //Field EnumUser.$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

tableswitchis 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.

tableswitch如果有三个或更多开关情况,则使用它,因为它执行更有效的恒定时间查找与lookupswitch的线性搜索。从技术上讲,javac 在使用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.

推测:我手头没有 Eclipse 的编译器来测试,但我想它不会打扰合成类,只是使用lookupswitch. 或者它可能需要比原始提问者在“升级”到tableswitch.

回答by TDJoe

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:

如果没有 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;
         }
     }

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

因为 case 语句必须是编译时常量(例如不是方法调用)。在这种情况下,如果A切换了 的顺序,foo()则会为两个打印“一”,反之亦然。

回答by Michael Borgwardt

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

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

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().

上面将创建三个类文件,一个用于基本枚举类,一个用于 YEA 和 NAY,用于保存 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.

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

回答by vicsz

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

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

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).

所以每当你定义一个新的 Enumeration 时,Java 编译器都会为你创建一个对应的 Class 文件。(无论枚举多么简单)。

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

没有办法解决这个问题,否则不使用枚举。

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

如果空间很宝贵,你总是可以使用常量来代替。

回答by Chandra Sekhar

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

考虑到并非所有 Java 开发人员都知道 Java 的这种行为,因此我创建了几个视频来解释 Java 中的 Switch 语句是如何工作的。

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

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

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

回答by dfa

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

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