73

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?

Community
  • 1
  • 1
Pool
  • 11,999
  • 14
  • 68
  • 78
  • Class$n class files are anonymous inner classes. I've made heavy use of enums and have not seen this. Could you post a source file that does this? – Devon_C_Miller Dec 02 '09 at 18:10
  • What kind of "bloat" are you worried about? Hopefully not disk space. :) If it is download size, can I recommend `.pack200.gz` (I naively packed CORBA as an experiment the other day - that most famous of Java bloats comes in at under 48K). – Tom Hawtin - tackline Dec 02 '09 at 20:29
  • Yes, it's download size of the packed Jar file for an applet. I'm trying to take steps to compress it where possible (without any source level changes). Will pack200 work for this case? – Pool Dec 02 '09 at 20:40
  • The first link is broken now. – Tiger Mar 07 '22 at 13:11

6 Answers6

66

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.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • An anecdote: I ran into this today (an in particular Eclipse's [JDK 8] compiler not doing it) in conjunction with this behaviour: https://coderanch.com/t/457290/Testing/Test-class-public-constructor -- This means that tests which run locally fail somewhat inexplicably on Jenkins because the latter isn't strictly enough configured, AND the compiler behaves differently than your local one. Since we don't have full control over our build, we will probably have to revert to avoiding switching enums in unit tests for the time being. – Rick Moritz Sep 02 '16 at 12:11
10

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.

TDJoe
  • 1,388
  • 1
  • 10
  • 15
6

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.

Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
1

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.

vicsz
  • 9,552
  • 16
  • 69
  • 101
1

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.

Chandra Sekhar
  • 18,914
  • 16
  • 84
  • 125
-1

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);
}
dfa
  • 114,442
  • 31
  • 189
  • 228