Switch

In Java a switch statement is a compact kind of if statement that chooses one of several options. The JVM has two instructions for performing switches, and both are for switching on integers. Starting as a preview in Java 17, and then officially added in Java 21, there are two helper bootstrap methods to allow switching on types (plus strings and integers), and enum values. Technically, these still use one of the instructions for switching on integers, but the bootstrap methods create a dynamic call that can take a class, string, integer, or enum and generate an integer that can be switched on.

Integer Switching

Java has two special instructions for switching on integer values, and apart from a few recent special features for switching on enumerations and classes, switch statements are ultimately implemented as a series of if statements.

The special switch instructions shouldn’t concern you as a JFuncMachine user, since it abstracts those away, except that it is helpful to know the two ways switching over ints can work as it may help guide your code generation.

First, there is a table switch, which assumes that the integers are mostly consecutive. If an int is within the range of the table values (say it is -5 to 5), the JVM adjusts the value so that the lowest bound becomes 0, and then looks it up in an array of locations that point to the code for each switch possibility. If the integers are too far apart, the lookup table will have long gaps that are filled with the pointers to the default handler.

When there are big gaps between the values, it may be better to use a lookup switch, which takes a sorted array of values and pointers, and uses a binary search to try to find the value. This allows for very large gaps between values, does the log~2 n searches.

JFuncMachine will automatically choose which option to use by counting the number of gaps between call values, and if the count exceeds some definable threshold (default is 10), it changes over to a lookup switch.

Creating Integer Switches

To create a switch you create a new IntSwitch giving it the value to switch on and an array of IntSwitchCase objects specifying each switch value and the expression to return for each value. Finally, you specify a default value, which is the value to return if the switch value doesn’t match any of the cases.

The following method performs a switch on a small set of integers, which result in a table switch. The value of each case is a string, so the overall type of the switch is a string. Notice that the switch cases don’t have to be in order:

MethodDef method = new MethodDef("switchtest", Access.PUBLIC, new Field[] {
        new Field("x", SimpleTypes.INT)},
        SimpleTypes.STRING,
        new IntSwitch(new GetValue("x", SimpleTypes.INT),
                new IntSwitchCase[] {
                    new IntSwitchCase(2, new StringConstant("larry")),
                    new IntSwitchCase(1, new StringConstant("moe")),
                    new IntSwitchCase(3, new StringConstant("curly")),
                },
            new StringConstant("nobody")));

This switch has values that have more than 10 total gaps, so it ends up resulting in a lookup switch. There is nothing in the actual definition that specified table vs lookup switch, that’s just how JFuncMachine ends up implementing it:

MethodDef method = new MethodDef("switchtest", Access.PUBLIC, new Field[] {
        new Field("x", SimpleTypes.INT)},
        SimpleTypes.STRING,
        new IntSwitch(new GetValue("x", SimpleTypes.INT),
                new IntSwitchCase[] {
                        new IntSwitchCase(42, new StringConstant("larry")),
                        new IntSwitchCase(1, new StringConstant("moe")),
                        new IntSwitchCase(73, new StringConstant("curly")),
                },
                new StringConstant("nobody")));

Creating Type Switches

The TypeSwitch class in JFuncMachine can only be used in Java 17 and later, and before Java 21 requires a property to be set to allow preview features. It provides a more robust way of switching including having additional tests after the initial switch condition.

Each TypeSwitchCase takes an object, which can be an ObjectType, a String or an Integer, and an expression to execute if the item matches the value being switched on.

Here is an example of this simple version of a type switch. It returns a value based on whether the value being switched on is a boolean, a double, or a character. It does not care about what that value is:

MethodDef method = new MethodDef("switchtest", Access.PUBLIC, new Field[] {
        new Field("x", new ObjectType())},
        SimpleTypes.STRING,
        new TypeSwitch(new GetValue("x", new ObjectType()),
                new TypeSwitchCase[] {
                    new TypeSwitchCase(new ObjectType("java.lang.Boolean"), new StringConstant("larry")),
                    new TypeSwitchCase(new ObjectType("java.lang.Double"), new StringConstant("moe")),
                    new TypeSwitchCase(new ObjectType("java.lang.Character"), new StringConstant("curly")),
                },
            new StringConstant("nobody")));

You can also specify additional tests for a type switch. For example, if you are doing a pattern match, you can match on a particular class, and then use an additional test to see if the fields in the object match the pattern. If not, you let the switch find the next match for that class.

Here is an example that matches on classes, and if the class is a char, it examines the char value. It also matches on a particular string value and a particular int value:

MethodDef method = new MethodDef("switchtest", Access.PUBLIC, new Field[] {
        new Field("x", new ObjectType())},
        SimpleTypes.STRING,
        new TypeSwitch(new GetValue("x", new ObjectType()),
                new TypeSwitchCase[] {
                        new TypeSwitchCase((ObjectType)SimpleTypes.BOOLEAN.getBoxType(), new StringConstant("larry")),
                        new TypeSwitchCase((ObjectType)SimpleTypes.DOUBLE.getBoxType(), new StringConstant("moe")),
                        new TypeSwitchCase((ObjectType)SimpleTypes.CHAR.getBoxType(),
                                new BinaryComparison(Tests.EQ,
                                    new GetValue("$caseMatchVar", SimpleTypes.CHAR.getBoxType()),
                                    new CharConstant('c')),
                                new StringConstant("curly")),
                        new TypeSwitchCase((ObjectType)SimpleTypes.CHAR.getBoxType(),
                                new BinaryComparison(Tests.EQ,
                                    new GetValue("$caseMatchVar", SimpleTypes.CHAR.getBoxType()),
                                    new CharConstant('s')),
                                new StringConstant("shemp")),
                        new TypeSwitchCase((ObjectType)SimpleTypes.CHAR.getBoxType(),
                                new BinaryComparison(Tests.EQ,
                                    new GetValue("$caseMatchVar", SimpleTypes.CHAR.getBoxType()),
                                    new CharConstant('j')),
                                new StringConstant("curly joe")),
                        new TypeSwitchCase("foobar", new StringConstant("barbaz")),
                        new TypeSwitchCase(42, new StringConstant("universe"))
                },
                new StringConstant("nobody")));

Creating Enum Switches

An enum switch is a switch that matches enum values for a specific enum class. It does not currently support additional tests. Here is a simple example of an enum switch. The first argument in the EnumSwitchCase constructor is the name of the field to match.

MethodDef method = new MethodDef("switchtest", Access.PUBLIC, new Field[] {
        new Field("x", new ObjectType(ToyEnum.class))},
        SimpleTypes.STRING,
                new EnumSwitch(new GetValue("x", new ObjectType(ToyEnum.class)),
                new EnumSwitchCase[] {
                    new EnumSwitchCase("Moe", new StringConstant("moe")),
                    new EnumSwitchCase("Larry", new StringConstant("larry")),
                    new EnumSwitchCase("Curly", new StringConstant("curly")),
                    new EnumSwitchCase("Shemp", new StringConstant("shemp")),
                    new EnumSwitchCase("CurlyJoe", new StringConstant("curly joe")),
                },
            new StringConstant("nobody")));