benf.org :  other :  cfr :  How is switch-on-enum implemented?

Switch on Enum

Enums

I looked at how Enums are implemented in java here.

Switch

Java has two bytecode switch instructions - TABLESWITCH and LOOKUPSWITCH. They both map from an integer (the value you're switching on) to a target bytecode address.

Given that Java's enums are objects, and not integers, how does the following get compiled?

    public int test0(EnumTest1 e) {
        switch (e) {
            case FOO:
                return 1;
            case BAP:
                return 2;
        }
        return 0;
    }

It's a little more complicated than you might expect! (and involves an extra level of indirection)

The first enum -> integer function that springs to mind for an integer is .ordinal(). However - there are a couple of problems with this:

So we need a lookup function which isn't dependant on the ordinal of the enum value being fixed (i.e. resolves it at run time), and can cope with a field of the enum being removed.

Decompilation of above

(As of 0_7 cfr will correctly re-sugar switch on enum to the original code, unless you specify not to on the command line - here we specify not to...)

Desugaring of switch statement

The switch statement above is changed to switch on an array lookup using the run time ordinal of the enum

public int test0(EnumTest1 e)
{
    switch (SwitchTest2$1.$SwitchMap$org$benf$cfr$tests$EnumTest1[e.ordinal()]) {
        case 1: {
            return 1;
        }
        case 2: {
            return 2;
        }
        default: 
    }
    return 0;
}

This array is a static final member of an inner class, and is populated at run time to avoid the issues above... (Yes, there's a new inner class per switch-on-enum!)

Inner class containing indirect array lookup

class SwitchTest2$1
extends Object
{
// Fields
static final /* synthetic */ int[] $SwitchMap$org$benf$cfr$tests$EnumTest1;
// Methods

static void <clinit>()
{
    SwitchTest2$1.$SwitchMap$org$benf$cfr$tests$EnumTest1 = new int[EnumTest1.values().length];
    try {
        SwitchTest2$1.$SwitchMap$org$benf$cfr$tests$EnumTest1[EnumTest1.FOO.ordinal()] = 1;
    }
    catch (NoSuchFieldError unnamed_local_ex_0) {
        // empty catch block
    }
    try {
        SwitchTest2$1.$SwitchMap$org$benf$cfr$tests$EnumTest1[EnumTest1.BAP.ordinal()] = 2;
    }
    catch (NoSuchFieldError unnamed_local_ex_0) {
        // empty catch block
    }
}

}

Note that NoSuchFieldErrors are ignored.

So the actual switch is done on an integer which is just the location of the target case in the original switch statement.....


Last updated 08/2012