├── LICENSE.txt ├── README.adoc ├── for-and-enhanced-for-loop-internals.adoc ├── try-with-resource-internals.adoc ├── boxing-and-wrapper-classes-for-primitive-types.adoc ├── enum-internals.adoc ├── nested-classes.adoc └── switch-case-internals.adoc /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ndru83/desugaring-java/HEAD/LICENSE.txt -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Desugaring Java 2 | 3 | Desugaring Java is series of articles exploring the ever-fascinating internals of the Java language and its libraries. 4 | Exploring some of the clever solutions, nasty hacks and compiler trickery that make up the language features programmers all use, but give little thought to. -------------------------------------------------------------------------------- /for-and-enhanced-for-loop-internals.adoc: -------------------------------------------------------------------------------- 1 | = Java `for` loop internals 2 | 3 | == Regular `for` loops 4 | 5 | For statements are essentially compiled into equivalent while loops. 6 | 7 | [source,java] 8 | .Original code 9 | ---- 10 | for (int i = 0; i < 5; ++i) { 11 | // ... 12 | } 13 | ---- 14 | 15 | [source,java] 16 | .Compiled equivalent 17 | ---- 18 | int i = 0; 19 | while (i < 5) { 20 | // ... 21 | ++i; 22 | } 23 | ---- 24 | 25 | The `for` and `while` statements above both compile into the exact same bytecode. 26 | 27 | [source,bytecode] 28 | .Bytecode 29 | ---- 30 | 0: iconst_0 31 | 1: istore_1 32 | 2: iload_1 33 | 3: iconst_5 34 | 4: if_icmpge 13 35 | // ... 36 | 7: iinc 1, 1 37 | 10: goto 2 38 | 13: return 39 | ---- 40 | 41 | == Enhanced `for` loops (over arrays) 42 | 43 | Enhanced `for` loops over arrays compile into while loops that are almost identical to that of regular `for` loops iterating over an entire array. 44 | 45 | [source,java] 46 | .Original code 47 | ---- 48 | for (String value : arrayExpr) { 49 | // ... 50 | } 51 | ---- 52 | 53 | [source,java] 54 | .Compiled equivalent 55 | ---- 56 | $array = arrayExpr; 57 | $length = $array.length; 58 | $i = 0; 59 | while ($i < $length) { 60 | String value = $array[$i]; 61 | // ... 62 | ++$i; 63 | } 64 | ---- 65 | 66 | Note, that the result of the inital array expression needs to be stored in an unnamed variable, because the re-evaluation of the expression might not be side-effect free. 67 | 68 | == Enhanced `for` loops (over `Iterables`) 69 | 70 | Enhanced `for` loops over iterables compile into a while loop equivalent using the iterable / iterator api. 71 | 72 | [source,java] 73 | .Original code 74 | ---- 75 | for (String value : iterable) { 76 | // ... 77 | } 78 | ---- 79 | 80 | [source,java] 81 | .Compiled equivalent 82 | ---- 83 | $iterator = iterable.iterator(); // 1 84 | while ($iterator.hasNext()) { // 2 85 | String value = $iterator.next(); // 3 86 | // ... 87 | } 88 | ---- 89 | 90 | . The `Iterator` object is retrived. 91 | . The loop will continue as long as `hasNext()` returns `true`. 92 | . The Iterator's `next()` method is called to retrieve the current item and to advance the iterator. -------------------------------------------------------------------------------- /try-with-resource-internals.adoc: -------------------------------------------------------------------------------- 1 | = Try-with-resource internals 2 | 3 | == Basic try-with-resource without catch and finally blocks 4 | 5 | [source,java,linenums] 6 | .Original code 7 | ---- 8 | try (AutoCloseable resource = new ClosableResource()) { 9 | // ... 10 | } 11 | ---- 12 | 13 | [source,java,linenums] 14 | .Compiled code equivalent 15 | ---- 16 | AutoCloseable resource = new ClosableResource(); 17 | Throwable $exception = null; 18 | 19 | try { 20 | // ... 21 | } catch (Throwable $e) { 22 | $exception = $e; 23 | throw $e; 24 | } finally { 25 | if (resource != null) { 26 | if ($exception != null) { 27 | try { 28 | resource.close(); 29 | } catch (Throwable $e) { 30 | $exception.addSuppressed($e); 31 | } 32 | } else { 33 | resource.close(); 34 | } 35 | } 36 | } 37 | ---- 38 | 39 | NOTE: Local variables starting with '$' in the above code are only named for clarity. These variables are otherwise unnamed, referenced only by slot number. 40 | 41 | == Try-with-resource with catch and finally blocks 42 | 43 | Catch and finally block are compiled as parts of a regular try-catch-finally block wrapping the try-with-resource block. 44 | 45 | [source,java,linenums] 46 | .Original code 47 | ---- 48 | try (AutoCloseable resource = new ClosableResource()) { 49 | // ... 50 | } catch (Exception1 e) { 51 | // ... 52 | } catch (Exception2 e) { 53 | // ... 54 | } finally { 55 | // ... 56 | } 57 | ---- 58 | 59 | [source,java,linenums] 60 | .Compiled code equivalent (using basic try-with-resource for code brevity) 61 | ---- 62 | try { 63 | try (AutoCloseable resource = new ClosableResource()) { 64 | // ... 65 | } 66 | } catch (Exception1 e) { 67 | // ... 68 | } catch (Exception2 e) { 69 | // ... 70 | } finally { 71 | // ... 72 | } 73 | ---- 74 | 75 | == Try-with-resource on multiple resources 76 | 77 | Multiple resources are compiled as nested try-with-resource code blocks. 78 | 79 | [source,java,lineums] 80 | .Original code 81 | ---- 82 | try (AutoCloseable resource1 = new ClosableResource(); 83 | AutoCloseable resource2 = new ClosableResource()) { 84 | // ... 85 | } 86 | ---- 87 | 88 | [source,java,linenums] 89 | .Compiled code equivalent (using basic try-with-resource for code brevity) 90 | ---- 91 | try (AutoCloseable resource1 = new ClosableResource()) { 92 | try (AutoCloseable resource2 = new ClosableResource()) { 93 | // ... 94 | } 95 | } 96 | ---- 97 | 98 | 99 | -------------------------------------------------------------------------------- /boxing-and-wrapper-classes-for-primitive-types.adoc: -------------------------------------------------------------------------------- 1 | = Internals of Boxing and Wrapper Classes for Primitive Types 2 | 3 | == Wrapper Classes for Primitive Types 4 | 5 | The types `Boolean`, `Byte`, `Short`, `Character`, `Integer`, `Long`, `Float` and `Double` are internally really nothing more than wrapper classes for primitive values. In each of these classes, the primitive value itself is stored in a private final field named `value`. See an excerpt taken from the implementation of Integer as an example. 6 | 7 | [source,java] 8 | ---- 9 | public final class Integer extends Number implements Comparable { 10 | 11 | // Other methods omitted for brevity 12 | 13 | private final int value; 14 | 15 | public Integer(int value) { 16 | this.value = value; 17 | } 18 | } 19 | ---- 20 | 21 | == Autoboxing and unboxing 22 | 23 | The java language provides automatic conversion between primitive types and their wrapper counterparts. The conversion from primitive type to wrapped type is called autoboxing and the conversion of wrapped types to primitive types is referred to as unboxing. 24 | 25 | Autoboxing of primitive types into wrapper object is internally compiled as calls to a static `valueOf` conversion method on the respective boxed classes. The unboxing such wrapper object into primitive types, on the other hand, are compiled as calls to conversion methods of the boxed instances. 26 | 27 | The following example shows the actual implementation behind boxing and unboxing an `int` value. 28 | 29 | [source,java] 30 | .Original code 31 | ---- 32 | Integer boxedInt = 1; 33 | int primitive = boxed; 34 | ---- 35 | 36 | [source,java] 37 | .Compiled equivalent 38 | ---- 39 | Integer boxed = Integer.valueOf(1); 40 | int primitive = boxed.intValue(); 41 | ---- 42 | 43 | Refer to the following table for the names of the methods used for unboxing each type of boxed object. 44 | 45 | |=== 46 | |Boxed type|Method used for unwrapping 47 | 48 | |`Boolean` |`boolean booleanValue()` 49 | |`Byte` |`byte byteValue()` 50 | |`Short` |`short shortValue()` 51 | |`Character`|`char charValue()` 52 | |`Integer` |`int intValue()` 53 | |`Long` |`long longValue()` 54 | |`Float` |`float floatValue()` 55 | |`Double` |`double doubleValue()` 56 | |=== 57 | 58 | == Modifying boxed objects 59 | 60 | Because boxed objects are immutable by design, operations on these types generally require the unboxing and re-boxing of the primitive value. One such example can be seen in the following code sample, where the variable `boxed` is incremented by one. 61 | 62 | [source,java] 63 | .Original code 64 | ---- 65 | Integer boxed = 1; 66 | ++boxed; 67 | ---- 68 | 69 | [source,java] 70 | .Compiled equivalent 71 | ---- 72 | Integer boxed = Integer.valueOf(1); 73 | boxed = Integer.valueOf(boxed.intValue() + 1); 74 | ---- 75 | 76 | == Caching of boxed objects 77 | 78 | For performance reasons, wrapper object representing the following ranges of primitive values are cached by the Java Runtime. 79 | 80 | |=== 81 | |Type|Range 82 | 83 | |bool |`true`, `false` 84 | |char |`\u0000` to `\u007f` 85 | |byte, short, int, long*|`-128` to `127` 86 | |=== 87 | 88 | Creating wrapper objects for values in the above ranges by either autoboxing or by calling a type's static `valueOf` method is always expected to yield same cached object instance. 89 | 90 | [source,java] 91 | ---- 92 | Integer a1 = 127, a2 = 127; 93 | Integer b1 = 128, b2 = 128; 94 | 95 | assert(a1 == a2); 96 | assert(a1 == Integer.valueOf(127)); 97 | 98 | assert(a1.equals(a2)); 99 | assert(b1.equals(b2)); 100 | ---- 101 | 102 | NOTE: `Long` wrapper objects created for the listed range of values are generally cached, but this is not required by the Java Specification. 103 | 104 | No such guareties should be expected for values outside the listed ranges and for objects created with a wrapper type's constructor. The following statements therefore will most likely print "false". 105 | 106 | [source,java] 107 | ---- 108 | Integer a = 127; 109 | Integer b1 = 128, b2 = 128; 110 | 111 | System.out.println(a == new Integer(127)); // false 112 | 113 | System.out.println(b1 == b2); // false 114 | System.out.println(b1 == Integer.valueOf(128)); // false 115 | System.out.println(b1 == new Integer(128)); // false 116 | ---- 117 | 118 | Internally, the caching of wrapper objects for primitive values is implemented in a given type's static `valueOf` method. This behavior extends to objects created by autoboxing, because the boxing semantics involve the calling the appropriate `valueOf` method for a given value type. The caches are eagerly populated at the loading of the wrapper class. 119 | 120 | [source,java] 121 | .Caching as implemented in OpenJDK's Integer.valueOf() 122 | ---- 123 | public static Integer valueOf(int i) { 124 | if (i >= IntegerCache.low && i <= IntegerCache.high) 125 | return IntegerCache.cache[i + (-IntegerCache.low)]; 126 | return new Integer(i); 127 | } 128 | ---- 129 | -------------------------------------------------------------------------------- /enum-internals.adoc: -------------------------------------------------------------------------------- 1 | = Java Enum Internals 2 | 3 | == Basic enums 4 | 5 | Consider the following basic enum declaration: 6 | 7 | [source,java] 8 | ---- 9 | public enum Example { 10 | ONE, TWO, THREE 11 | } 12 | ---- 13 | 14 | At the highest level, all enums are compiled into a final class extending `Enum`. 15 | 16 | [source,java] 17 | ---- 18 | public final class Example extends Enum { 19 | ... 20 | } 21 | ---- 22 | 23 | The class will have a private constructor matching the signature of it's base class' constructor, accepting an String constant name and an int constant ordinal. 24 | 25 | [source,java] 26 | ---- 27 | private Example(String name, int ordinal) { 28 | super(name, ordinal); 29 | } 30 | ---- 31 | 32 | Declared enum constants are compiled into `public static final` fields. Any use of the enum constants in other parts of your source code will compile to use these static fields. In addition to the public enum constants, a private array field will also be generated, intended hold a reference to all the enum constants. 33 | 34 | [source,java] 35 | ---- 36 | public static final Example ONE; 37 | public static final Example TWO; 38 | public static final Example THREE; 39 | private static final Example[] $VALUES; 40 | ---- 41 | 42 | The enum constant fields are initialized in the class' static constructor. Each constant reference is created by calling the class' private constructor with the exact name of the constant and an ordinal number starting at 0 and incremented by 1 for every consecutive constant created. The `$VALUES` array field is initialized to hold a reference to every enum constant. 43 | 44 | [source,java] 45 | ---- 46 | static { 47 | Example.ONE = new Example("ONE", 0); 48 | Example.TWO = new Example("TWO", 1); 49 | Example.TWO = new Example("THREE", 2); 50 | Example.$VALUES = new Example[] { 51 | Example.ONE, 52 | Example.TWO, 53 | Example.THREE 54 | }; 55 | } 56 | ---- 57 | 58 | The `values()` method is added to allow access to a clone of the `$VALUES` array. 59 | 60 | [source,java] 61 | ---- 62 | public static Example[] values() { 63 | return (Example[]) $VALUES.clone(); 64 | } 65 | ---- 66 | 67 | NOTE: The `Class` class internally implements it's `getEnumConstants()` method as a reflective call to the enum's `values()` method. 68 | 69 | Finally the enum is generated a `valueOf` method which simply dispatches the call to a static method of the same name defined on the Enum base class. 70 | 71 | [source,java] 72 | ---- 73 | public static Example valueOf(String name) { 74 | return Enum.valueOf(Example.class, name); 75 | } 76 | ---- 77 | 78 | Having enumerated all members of a compiled enum review the entire class as compiled by javac: 79 | 80 | [source,java] 81 | ---- 82 | public final class Example extends Enum { 83 | 84 | public static final Example ONE; 85 | public static final Example TWO; 86 | public static final Example THREE; 87 | 88 | private static final Example[] $VALUES; 89 | 90 | private Example(String name, int ordinal) { 91 | super(name, ordinal); 92 | } 93 | 94 | static { 95 | Example.ONE = new Example("ONE", 0); 96 | Example.TWO = new Example("TWO", 1); 97 | Example.TWO = new Example("THREE", 2); 98 | Example.$VALUES = new Example[] { 99 | Example.ONE, 100 | Example.TWO, 101 | Example.THREE 102 | }; 103 | } 104 | 105 | public static Example[] values() { 106 | return (Example[]) $VALUES.clone(); 107 | } 108 | 109 | public static Example valueOf(String name) { 110 | return Enum.valueOf(Example.class, name); 111 | } 112 | 113 | } 114 | ---- 115 | 116 | == Enums with non-default constructors 117 | 118 | For enums with non-default constructors consider the following enum declaration: 119 | 120 | [source,java] 121 | ---- 122 | public enum Example { 123 | 124 | ONE(100), TWO(200), THREE("300", 300); 125 | 126 | Example(int someInt) { 127 | // ctor body 1 128 | } 129 | 130 | Example(String someString, int someInt) { 131 | // ctor body 2 132 | } 133 | } 134 | ---- 135 | 136 | Non-default constructor(s) on enum classes are compiled by pre-pending the ones appearing in the source code with an additional String and int parameters, representing the name and an ordinal value of a given enum constant. These parameters are used when invoking constructor of the enum supertype. 137 | 138 | [source,java] 139 | ---- 140 | private Example(String name, int ordinal, int someInt) { 141 | super(name, ordinal); 142 | // ctor body 1 143 | } 144 | 145 | private Example(String name, int ordinal, String someString, int someInt) { 146 | super(name, ordinal); 147 | // ctor body 2 148 | } 149 | ---- 150 | 151 | NOTE: That these additional parameters `name` and `ordinal` don't actually have names in the byte code and therefore can never clash with the names of other constructor parameters. 152 | 153 | Enum constant fields will be initialized by calls to the constructor appropriate for the given constant's constructor. 154 | 155 | [source,java] 156 | ---- 157 | static { 158 | Example.ONE = new Example("ONE", 0, 100); 159 | Example.TWO = new Example("TWO", 1, 200); 160 | Example.TWO = new Example("THREE", 2, "300", 300); 161 | Example.$VALUES = new Example[] { 162 | Example.ONE, 163 | Example.TWO, 164 | Example.THREE 165 | }; 166 | } 167 | ---- 168 | 169 | Other than the differences outlined above, enum classes with non-default constructors are compiled just like basic enums. 170 | 171 | == Enum constants with class bodies 172 | 173 | For enum constants with class bodies, consider the following example: 174 | 175 | [source,java] 176 | ---- 177 | public enum MessageStatus { 178 | 179 | INIT, 180 | PENDING, 181 | COMPLETED, 182 | FAILED { 183 | @Override 184 | public boolean isErroneous() { 185 | return true; 186 | } 187 | }; 188 | 189 | public boolean isErroneous() { 190 | return false; 191 | } 192 | 193 | } 194 | ---- 195 | 196 | Class bodies of enum constants are compiled as anonymous classes extending the original enum. To allow this, the base enum itself will no longer be marked as a `final` and is compiled into a class similar to the following: 197 | 198 | [source,java] 199 | ---- 200 | public class MessageStatus extends Enum { 201 | 202 | // Omitting some members for brevity 203 | // (...) 204 | 205 | static { 206 | MessageStatus.INIT = new MessageStatus("INIT", 0); 207 | MessageStatus.PENDING = new MessageStatus("PENDING", 1); 208 | MessageStatus.COMPLETED = new MessageStatus("COMPLETED", 2); 209 | MessageStatus.FAILED = new MessageStatus("FAILED", 3) { 210 | @Override 211 | public boolean isErroneous() { 212 | return true; 213 | } 214 | }; 215 | MessageStatus.$VALUES = new MessageStatus[] { 216 | MessageStatus.INIT, 217 | MessageStatus.PENDING, 218 | MessageStatus.COMPLETED, 219 | MessageStatus.FAILED 220 | }; 221 | } 222 | 223 | public boolean isErroneous() { 224 | return false; 225 | } 226 | } 227 | ---- 228 | 229 | == Enums with abstract methods 230 | 231 | Enums in java may also declare `abstract` methods or inherit them by implementing interfaces. Enum constant of these classes *must*, however, provide an implementation for each `abstract` method. See the following code as an example: 232 | 233 | [source,java] 234 | ---- 235 | interface Bar { 236 | void doAThing(); 237 | } 238 | 239 | public enum Foo implements Bar { 240 | 241 | CONSTANT_A { 242 | @Override 243 | public void doAThing() { 244 | // ... 245 | } 246 | }, 247 | CONSTANT_B { 248 | @Override 249 | public void doAThing() { 250 | // ... 251 | } 252 | } 253 | } 254 | ---- 255 | 256 | Such enums will be compiled into `abstract` classes implementing interfaces, similar to the one presented below: 257 | 258 | [source,java] 259 | ---- 260 | public abstract class Foo extends Enum implements Bar { 261 | 262 | // Omitting members for brevity 263 | // (...) 264 | 265 | static { 266 | Foo.CONSTANT_A = new Foo("CONSTANT_A", 0) { 267 | @Override 268 | public void doAThing() { 269 | // ... 270 | } 271 | }; 272 | Foo.CONSTANT_B = new Foo("CONSTANT_B", 1) { 273 | @Override 274 | public void doAThing() { 275 | // ... 276 | } 277 | }; 278 | MessageStatus.$VALUES = new MessageStatus[] { 279 | MessageStatus.CONSTANT_A, 280 | MessageStatus.CONSTANT_B 281 | }; 282 | } 283 | } 284 | ---- 285 | 286 | == Further enum-related internals 287 | 288 | === Internals of `Class.getEnumConstants()` 289 | 290 | An alternative way for retrieving the enum constant of an enum is by using the `T[] getEnumConstants()` provided by the enum's `Class` object. Interestingly, `Class`, in turn, relies on reflection to call the actual enum class's `values()` method in order to retrieve the constant object. 291 | 292 | [source,java] 293 | .Internal implementation of `Class.getEnumConstants()` 294 | ---- 295 | private transient volatile T[] enumConstants; 296 | 297 | public T[] getEnumConstants() { 298 | T[] values = getEnumConstantsShared(); 299 | return (values != null) ? values.clone() : null; 300 | } 301 | 302 | T[] getEnumConstantsShared() { 303 | T[] constants = enumConstants; 304 | if (constants == null) { 305 | if (!isEnum()) return null; 306 | try { 307 | final Method values = getMethod("values"); 308 | java.security.AccessController.doPrivileged( 309 | new java.security.PrivilegedAction<>() { 310 | public Void run() { 311 | values.setAccessible(true); 312 | return null; 313 | } 314 | }); 315 | @SuppressWarnings("unchecked") 316 | T[] temporaryConstants = (T[])values.invoke(null); 317 | enumConstants = constants = temporaryConstants; 318 | } 319 | // These can happen when users concoct enum-like classes 320 | // that don't comply with the enum spec. 321 | catch (InvocationTargetException | NoSuchMethodException | 322 | IllegalAccessException ex) { return null; } 323 | } 324 | return constants; 325 | } 326 | ---- 327 | 328 | === Internals of Enum.valueOf(Example.class, name) 329 | 330 | As stated previously, the generated `valueOf(String name)` method of enum classes call `Enum.valueOf(Class enumType, String name)` for resolving contants names into enum constant. This name resolution is even more complicated, bacause the `Enum.valueOf(Class enumType, String name)` method in turn relies on an package private _enum constant directory_ functionality maintained by the enum's `Class` object. 331 | 332 | [source,java] 333 | .Internal implementation of `Enum.valueOf(Class enumType, String name)` 334 | ---- 335 | public static > T valueOf(Class enumType, String name) { 336 | T result = enumType.enumConstantDirectory().get(name); 337 | if (result != null) 338 | return result; 339 | if (name == null) 340 | throw new NullPointerException("Name is null"); 341 | throw new IllegalArgumentException( 342 | "No enum constant " + enumType.getCanonicalName() + "." + name); 343 | } 344 | ---- 345 | 346 | The _enum constant directory_ is a lazily built `Map` structure stored by enum Class objects, mapping string names to references of actual enum constants. The `enumConstantDirectory()` method builds the lookup map from data returned by the very same reflection-based `getEnumConstantsShared()` method used by the previously discussed `Class.getEnumConstants()` method. 347 | 348 | [source,java] 349 | .Internal implementation of `Class.enumConstantDirectory()` 350 | ---- 351 | private transient volatile Map enumConstantDirectory; 352 | 353 | Map enumConstantDirectory() { 354 | Map directory = enumConstantDirectory; 355 | if (directory == null) { 356 | T[] universe = getEnumConstantsShared(); 357 | if (universe == null) 358 | throw new IllegalArgumentException( 359 | getName() + " is not an enum type"); 360 | directory = new HashMap<>(2 * universe.length); 361 | for (T constant : universe) { 362 | directory.put(((Enum)constant).name(), constant); 363 | } 364 | enumConstantDirectory = directory; 365 | } 366 | return directory; 367 | } 368 | ---- 369 | 370 | -------------------------------------------------------------------------------- /nested-classes.adoc: -------------------------------------------------------------------------------- 1 | = Nested Classes 2 | 3 | Nested classes are classes defined within classes. We distinguish between 4 different kinds of nested classes. 4 | 5 | * Static inner classes 6 | * Nested inner classes 7 | * Method-local inner classes 8 | * Anonymous Inner classes 9 | 10 | Regardless of type, all nested classes share a common feature in that they are compiled into their own separate class files. 11 | 12 | == Static inner classes 13 | 14 | The relation between static inner classes and their enclosing outer classes is purely structural. A static inner class has no special access to members of the enclosing class, the outer class is only used as a namespace prefix for the inner class. See the following code as an example, where the static a inner class called "Inner" is nested within a class called "Outer". 15 | 16 | [source,java] 17 | ---- 18 | public class Outer { 19 | public static class Inner { 20 | // ... 21 | } 22 | } 23 | ---- 24 | 25 | The above static inner class can be instantiated by the following expression: 26 | 27 | [source,java] 28 | ---- 29 | Outer.Inner inner = new Outer.Inner(); 30 | ---- 31 | 32 | As stated before, static inner classes are compiled into separate class files. In this case the `Inner` class will be renamed `Outer$Inner` and compiled into an `Outer$Inner.class` file. With the class hierarchy flattened, the actual compiled code instantiating the class will look like this: 33 | 34 | [source,java] 35 | .Compiled equivalent 36 | ---- 37 | Outer$Inner inner = new Outer$Inner(); 38 | ---- 39 | 40 | CAUTION: Defining classes with names similar to `Outer$Inner`, while not prohibited, should be avoided, because these names _may_ clash with other compiler generated class names. An example error message signaling such a problem would look something like the following: "Error: duplicate class: Outer.Inner". 41 | 42 | == Nested inner classes 43 | 44 | Nested inner classes, are all bound to a given instance of their outer class and have full access to all their members, including ones with private visibility. Like static inner classes, nested inner classes are also compiled into separate class files. See the following code as an example, where a nested inner class called "Inner" is nested within a class called "Outer". 45 | 46 | [source,java] 47 | ---- 48 | public class Outer { 49 | 50 | public int outerValue = 0; 51 | 52 | public void outerMethod(int value) { 53 | // ... 54 | } 55 | 56 | public class Inner { 57 | 58 | public void innerMethod(int value) { 59 | System.out.println(outerValue); 60 | outerValue = value; 61 | outerMethod(value); 62 | } 63 | } 64 | } 65 | ---- 66 | 67 | As stated previously, nested inner classes are bound to a specific instance of the outer class they are nested within. As can be seen in the following example, the instantiation of nested inner classes is handled through an instance of the outer class. 68 | 69 | [source,java] 70 | ---- 71 | Outer outer = new Outer(); 72 | Outer.Inner inner = outer.new Inner(); 73 | ---- 74 | 75 | Similar to static inner classes, nested inner classes are also renamed and compiled into separate class files. The compiler will also modify the inner class by adding an extra `final` field. This field is most often called `this$0` and is intended to hold a reference to an instance the outer class. The constructor signature of the inner class will be extended to accept an extra 0th parameter of the type of the outer class, and the the constructor code is added instructions that assign this parameter to the aforementioned `this$0` field. Statements that reference `public` members of the outer class will be modified to use reference stored in the `this$0` variable. The compiled equivalent of the example inner class will resemble the following: 76 | 77 | [source,java] 78 | ---- 79 | public class Outer$Inner 80 | 81 | final Outer this$0; 82 | 83 | public Outer$Inner(Outer this$0) { 84 | this.this$0 = this$0; 85 | } 86 | 87 | public void innerMethod(int value) { 88 | System.out.println(this$0.outerValue)); 89 | this$0.outerValue = value; 90 | this$0.outerMethod(value); 91 | } 92 | } 93 | ---- 94 | 95 | NOTE: The compiler will resolve name clashes between existing class fields and generated `this$0` identifiers, by appending `$` characters to the name of the synthetic field until the clash is finally resolved. 96 | 97 | Code used for instantiating the class is also be modified to pass a reference of the outer class as an argument of the newly introduced constructor parameter. The code compiled for the sample demonstrating the instantiation of nested inner classes will therefore resemble the following: 98 | 99 | [source,java] 100 | ---- 101 | Outer outer = new Outer(); 102 | Outer$Inner inner = new Outer$Inner(outer); 103 | ---- 104 | 105 | === Synthetic accessor methods (JDK 10 and older) 106 | 107 | Before the introduction of the notion of nestmates in Java 11, nested inner classes had no special privileged access to the `private` members of their enclosing class. To allow nested inner classes access to these members, the Java compiler essentially had to break the encapsulation of the outer class by introducing static accessor and mutator methods for each private member. This can be demonstrated in the following example: 108 | 109 | [source,java] 110 | ---- 111 | public class Outer { 112 | 113 | private int outerValue = 0; 114 | 115 | private void outerMethod(int value) { 116 | // ... 117 | } 118 | 119 | public class Inner { 120 | 121 | public void innerMethod(int value) { 122 | System.out.println(outerValue); 123 | outerValue = value; 124 | outerMethod(value); 125 | } 126 | } 127 | } 128 | ---- 129 | 130 | Examining the compiled equivalent of the `Outer` class, reveals a number of `static` helper methods, generated solely for providing the `Inner` class with access to various private class members. 131 | 132 | [source,java] 133 | .Compiled equivalent of the Outer class 134 | ---- 135 | public class Outer { 136 | 137 | private int outerValue = 0; 138 | 139 | private void outerMethod(int value) { 140 | // ... 141 | } 142 | 143 | // Synthetic accessor of outerValue 144 | static int access$000(Outer x0) { 145 | return x0.outerValue; 146 | } 147 | 148 | // Synthetic mutator of outerValue 149 | static int access$002(Outer x0, int x1) { 150 | return (x0.outerValue = x1); 151 | } 152 | 153 | // Synthetic delegate to outerMethod(int) 154 | static void access$100(Outer x0, int x1) { 155 | x0.outerMethod(x1); 156 | } 157 | 158 | } 159 | ---- 160 | 161 | The code of the `Inner` class is also modified to use the previously generated helper methods when accessing the otherwise restricted members of the outer class. 162 | 163 | [source,java] 164 | .Compiled equivalent of the nested inner class 165 | ---- 166 | public class Outer$Inner 167 | 168 | final Outer this$0; 169 | 170 | public Outer$Inner(Outer this$0) { 171 | this.this$0 = this$0; 172 | } 173 | 174 | public void innerMethod(int value) { 175 | System.out.println(Outer.access$000(this.this$0)); 176 | Outer.access$002(this.this$0, value); 177 | Outer.access$100(this.this$0, value); 178 | } 179 | } 180 | ---- 181 | 182 | CAUTION: Defining methods with names that start with the `access$` prefix should be avoided, because these names _may_ clash with compiler generated helper methods, resulting in compile-time time errors. An example error message signaling such a problem would look something like the following: "Error: The symbol access$000(Outer) conflicts with a compiler-synthesized symbol in Outer". 183 | 184 | === Nest-based access control (Java 11 and newer) 185 | 186 | To address the shortcomings of the implementation of nested inner classes discussed in the previous section, Java 11 introduced the notion of _nestmates_. Nestmates introduce a new access control mechanism that allows a nested inner classes (nest members) to access `private` members of their enclosing outer class (the nest host) without the use of synthetic accessor methods, if both classes belong to the same nest. Nests are internally implemented as class attributes on both the enclosing and the nested classes. The enclosing class will have a `NestMembers` attribute, listing the names of all the classes that are "enclosed" within the class. Conversely, the compiled nested inner class will have a `NestHost` attribute set to the name of the enclosing class. 187 | 188 | Nest-based access control functions as follows: A class named _A_ will have access to the `private` members of another class named _B_ if class _A_ has a `NestHost` class attribute set to the class name of _B_ AND class _B_ has a `NestMembers` class attribute containing the class name of `A`. 189 | 190 | Recompiling the example class presented under the synthetic methods section with Java 11 will output classes that accesses private members without the use of synthetic accessor methods. Access to private members of the enclosing class will be granted by the JVM based on the nestmate relationship encoded into classes' `NestMembers` and `NestHost` attributes. 191 | 192 | [source,java] 193 | .Compiled Java 11 equivalent of the enclosing outer class 194 | ---- 195 | public class Outer { 196 | 197 | // NestMembers attribute: Outer$Inner 198 | 199 | private int outerValue = 0; 200 | 201 | private void outerMethod(int value) { 202 | // ... 203 | } 204 | 205 | } 206 | ---- 207 | 208 | [source,java] 209 | .Compiled Java 11 equivalent of the nested inner class 210 | ---- 211 | 212 | public class Outer$Inner { 213 | 214 | // NestHost attribute: class Outer 215 | 216 | final Outer this$0; 217 | 218 | public Outer$Inner(Outer this$0) { 219 | this.this$0 = this$0; 220 | } 221 | 222 | public void innerMethod(int value) { 223 | System.out.println(this$0.outerValue)); 224 | this$0.outerValue = value; 225 | this$0.outerMethod(value); 226 | } 227 | } 228 | ---- 229 | 230 | 231 | == Method-local classes 232 | 233 | Method local classes are classes that are declared within the bodies of other methods. Method local classes defined within instance methods are essentially nested inner classes, while those defined within static methods are static inner classes. A method local class can only be referenced inside the method it was declared in. 234 | 235 | In addition to the capabilities of their respective implementations, method local classes can also reference `final` local variables from their enclosing method's lexical context. Since Java 8, this access also extends to effectively final local variables. These variables, while not explicitly declared `final`, are set only once in the context of a method. 236 | 237 | Captured local variables are internally stored in method local classes as `final` fields named `val$`, and followed by the name of the variable. The actual values of the captured variables are injected into the local class via synthetically added constructor parameters. Synthetic parameters will be added before other defined constructor parameters. 238 | 239 | The following code snippet is an example of a method local class capturing a local variable: 240 | 241 | [source,java] 242 | ---- 243 | public class Outer { 244 | 245 | public void printHello() { 246 | 247 | String greeting = "Hello!"; 248 | 249 | class Greeter { 250 | public void greet() { 251 | System.out.println(greeting); 252 | } 253 | } 254 | 255 | Greeter greeter = new Greeter(); 256 | greeter.greet(); // Prints "Hello!" 257 | } 258 | } 259 | ---- 260 | 261 | The above code is compiled into the semantic equivalent of the following nested inner class combination. 262 | 263 | [source,java] 264 | .Semantic equivalent 265 | ---- 266 | public class Outer { 267 | 268 | class Greeter { 269 | 270 | final String val$greeting; 271 | 272 | public Greeter(String $val1) { 273 | this.val$greeting = $val1; 274 | } 275 | 276 | public void greet() { 277 | System.out.println(this.val$greeting); 278 | } 279 | } 280 | 281 | public void printHello() { 282 | String greeting = "Hello!"; 283 | Greeter greeter = new Greeter(greeting); 284 | greeter.greet(); // Prints "Hello!" 285 | } 286 | } 287 | ---- 288 | 289 | NOTE: Parameters used to inject captured variables into method local classes are actually unnamed. The constructor parameter named `$val1` in the above code is only used for convenience. 290 | 291 | CAUTION: Defining fields in method local classes that start with the `val$` prefix should be avoided, because these names _may_ clash with compiler generated fields created to store the values of captured local variables. An example error message signaling such a problem would look something like the following: "Error while generating class Greeter (the symbol val$greeting conflicts with a compiler-synthesized symbol in Greeter)" 292 | 293 | == Anonymous inner classes 294 | 295 | TODO -------------------------------------------------------------------------------- /switch-case-internals.adoc: -------------------------------------------------------------------------------- 1 | = Java switch-case internals 2 | 3 | Switch-case statements are internally implemented with either `tableswitch` or `lookupswitch` bytecode instructions. Both instructions function by popping the stack for an integer value, and selecting a jump offset associated with the popped value. 4 | 5 | == The `tableswitch` instruction 6 | 7 | The `tableswitch` instruction is a variable length instruction used for selecting and executing a jump based on a jump-table defined over a sorted, continuous list of possible integer values. Tool, such as javap, might represent the `tableswitch` instruction textually in ways similar to the following example. 8 | 9 | [source] 10 | ---- 11 | tableswitch { // 3 to 5 12 | 3: 50 13 | 4: 39 14 | 5: 28 15 | default: 61 16 | } 17 | ---- 18 | 19 | The instruction described above will pop the stack for an integer value and for values ranging from 3 to 5 perform a jump to the noted program address. For values outside the defined 3 to 5 range, the instruction will preform a jump to the address noted under the default label. Note, that the actual bytecode instruction uses relative offset values rather than the absolute addresses reported by javap. 20 | 21 | The bytecode structure of the `tableswitch` instruction is as follows: 22 | 23 | [cols=",100%"] 24 | |=== 25 | |Data type |Description 26 | 27 | |ubyte8 |`tableswitch` opcode (0xAA) 28 | |- |Padding of (0-3) bytes so that the start of the default jump offset field falls on a address, which is a multiple of 4. 29 | |uint32 |Default jump offset 30 | |uint32 |Low value 31 | |uint32 |High value 32 | |uint32 |Jump offset 1, associated with value _low_ 33 | |... |... 34 | |uint32 |Jump offset n, associated with value _high_ (_low_ + n - 1) 35 | |=== 36 | 37 | The operations performed by the execution of the `switchtable` instruction can be illustrated with the following pseudo-code. 38 | 39 | [source,java] 40 | ---- 41 | int value = pop(); 42 | if (value < lowValue || value > highValue) { 43 | pc += defaultOffset; 44 | } else { 45 | pc += jumpOffset[value - lowValue]; 46 | } 47 | ---- 48 | 49 | The `pop()` expression in the above code represents the instruction popping the stack for an integer value and the `pc` variable stands for the _program counter_ register, storing the address of the next instruction to be executed by the JVM. 50 | 51 | == The `lookupswitch` instruction 52 | 53 | The `lookupswitch` instruction is used performing branching based on a list of key-offset pairs. This instruction is not as fast as `tableswitch` instruction, but allows branching on a list of individual values, rather than continuous ranges of values. This branching instruction essentially trades computational efficiency for space efficiency in cases where the branching must be performed on a sparse non-continuous set of values. 54 | 55 | A sample `lookupswitch` as printed by javap might look something like the following: 56 | 57 | [source] 58 | ---- 59 | lookupswitch { // 3 60 | 0: 36 61 | 1: 47 62 | 500: 58 63 | default: 69 64 | } 65 | ---- 66 | 67 | This instruction will pop the stack for an integer value, then will search it's list of key-jump offset pairs for a matching value. The list of pairs are sorted by key to allow a better-than-linear searching. 68 | 69 | If a matching key is found in the list, a jump will be performed using the associated jump offset. I case no matching key is found, the instruction will instead perform a jump based on the default jump offset of the instruction. 70 | 71 | Note, that the actual bytecode instruction uses relative offset values rather than the absolute addresses reported by javap. 72 | 73 | The bytecode structure of the `lookupswitch` instruction is as follows: 74 | 75 | [cols=",100%"] 76 | |=== 77 | |Data type |Description 78 | 79 | |ubyte8 |`lookupswitch` opcode (0xAB) 80 | |- |Padding of (0-3) bytes so that the start of the default jump offset field falls on a address, which is a multiple of 4. 81 | |uint32 |Default jump offset 82 | |uint32 |Number of key-offset pairs 83 | |uint32 |Value of key 1 84 | |uint32 |Jump offset for key 1 85 | |... |... 86 | |uint32 |Value of key n 87 | |uint32 |Jump offset for key n 88 | |=== 89 | 90 | == Choosing the right switch instruction 91 | 92 | When compiling a given switch statement the java compiler has to make a decision between either emitting a `tableswitch` or a `lookupswitch` instruction. This decision is based on the time/space complexity costs associated with each instruction. 93 | 94 | The compiler will stick with the `tableswitch` instruction as long as the space and time costs for doing so are lower or equal to the cost of using `lookupswitch`. 95 | 96 | The table used for calculating space/time costs for the two instruction types is the following: 97 | 98 | |=== 99 | |Opcode |Space cost |Time cost 100 | 101 | |`tableswitch` |4 + (hi - lo + 1)|3 102 | |`lookupswitch`|3 + 2 * nlabels |nlabels 103 | |=== 104 | 105 | The _hi_ and _lo_ parameters are the highest and lowest value switch keys used in the switch statement and _nlabels_ is the number of switch keys. 106 | 107 | The overall space/time cost of using a specific instruction is calculated by using the formula: `spaceCost + timeCost * 3`. 108 | 109 | NOTE: An interesting property the cost formula governing code compilation is that switch statements with a less than 3 labels and no gaps between the keys will compile as `lookupswitch` instructions. This is because, for such few labels, the calculated costs of `lookupswitch`, are still lower than those of the `tableswitch` alternative. The performance benefits of `tableswitch` only start to outperform `lookupswitch` as the number of labels reaches 3. 110 | 111 | == Using `tableswitch` over a non-continuous range of keys 112 | 113 | Choosing between `tableswitch` and `lookupswitch` based on space/time cost analysis can lead to situations where a `tableswitch` instruction needs to operate over a non-continuous range of key values. The compiler will resolve this problem by filling in the gaps between the sparse keys with jumps to the default label of the switch statement. 114 | 115 | See the following `lookupswitch` instruction over the sparse value set of 0, 1, 3 and 5 as an example. 116 | 117 | [source] 118 | ---- 119 | lookupswitch { // 4 120 | 0: 40 121 | 1: 51 122 | 3: 62 123 | 5: 73 124 | default: 84 125 | } 126 | ---- 127 | 128 | By inserting values 2 and 4 as jumps to the default offset (84), the instruction can be converted into a semantically equivalent `tableswitch` instruction: 129 | 130 | [source] 131 | ---- 132 | tableswitch { // 0 to 5 133 | 0: 40 134 | 1: 51 135 | 2: 84 // dummy case 136 | 3: 62 137 | 4: 84 // dummy case 138 | 5: 73 139 | default: 84 140 | } 141 | ---- 142 | 143 | Using a `tableswitch` instead of a `lookupswitch` in cases such as these is a decision to trade the space efficiency provided by sparse keys for the constant time lookup of a table-based approach. 144 | 145 | == Implementing java switches over byte, short, char, and int values 146 | 147 | Java switches over numeric values are amongst the java statements where the compiled form of the statement resemble very closely that of the original source code. 148 | 149 | Consider the following sample code as an example: 150 | 151 | [source,java] 152 | ---- 153 | switch (a) { 154 | case 0: 155 | System.out.println("zero"); 156 | break; 157 | case 1: 158 | System.out.println("one"); 159 | break; 160 | case 2: 161 | System.out.println("two"); 162 | break; 163 | default: 164 | System.out.println("other"); 165 | } 166 | ---- 167 | 168 | The code compiled from the above source is the following: 169 | 170 | [source] 171 | ---- 172 | 0: iload_0 173 | 1: tableswitch { // 0 to 2 174 | 0: 28 175 | 1: 39 176 | 2: 50 177 | default: 61 178 | } 179 | 28: ... // System.out.println("zero"); 180 | 36: goto 69 // break; 181 | 39: ... // System.out.println("one"); 182 | 47: goto 69 // break; 183 | 50: ... // System.out.println("two"); 184 | 58: goto 69 // break; 185 | 61: ... // System.out.println("other"); 186 | 69: return 187 | ---- 188 | 189 | Probably the first noticeable feature of the compiled code is, that case labels were removed from what used to be the switch body and are sorted and integrated into switch's tableswitch/lookupswitch instruction. 190 | 191 | Unlike the keys themselves, compiler will preserves the ordering each `case` block, inserting a `goto` instruction in place of every `break` statement. Each such `goto` instruction is set up to redirect the flow of execution to the fist statement after the switch construct. Any `case` blocks omitting the `break` statement will likewise be missing their respective `goto` instructions, allowing the execution to "fall through" into the next case label. 192 | 193 | In the absence of a default label, the compiled `tableswitch`/`lookupswitch` instruction will have it's _default jump offset_ set to the offset of the first statement following the switch construct. 194 | 195 | Switching over `long` values is not supported, unless the value is manually downcast to `int`. This limitation is most likely imposed because the `tableswitch` and `lookupswitch` structures store ranges and key values as unsigned 32-bit integers. 196 | 197 | == Implementing java switches over String values 198 | 199 | The first Java version to support switching over `Strings` values was JDK 7. Interestingly, this support was introduced with the help of some additional compiler trickery as none of underlying `tableswitch` and `lookupswitch` instructions actually work on anything other than numeric data. 200 | 201 | See the following switch as an example: 202 | 203 | [source,java] 204 | .Original code 205 | ---- 206 | switch (a) { 207 | case "aaa": 208 | System.out.println("aaa"); 209 | break; 210 | case "bbb": 211 | System.out.println("bbb"); 212 | break; 213 | case "ccc": 214 | System.out.println("ccc"); 215 | break; 216 | default: 217 | System.out.println("other"); 218 | } 219 | ---- 220 | 221 | Switch statements, like the one used in the above code are compiled as a series of two consecutive `switch` statements. The first `switch` branches over the hash codes of each `string` label, mapping them to a unique 0-base index, or in the case of missing values, the `-1` magic constant. Possible hash collisions are guarded against by performing additional equality checks against the label values. 222 | 223 | [source,java] 224 | .Compiled code, mapping string labels to id's 225 | ---- 226 | byte $var2 = -1; 227 | switch(a.hashCode()) { 228 | case 96321: 229 | if (a.equals("aaa")) { 230 | $var2 = 0; 231 | } 232 | break; 233 | case 97314: 234 | if (a.equals("bbb")) { 235 | $var2 = 1; 236 | } 237 | break; 238 | case 98307: 239 | if (a.equals("ccc")) { 240 | $var2 = 2; 241 | } 242 | } 243 | ---- 244 | 245 | The second half of the switch pair is a compiled version of the source code original, modified to switch over the numeric labels resolved by the previous switch. 246 | 247 | [source,java] 248 | .Compiled code, switching over mapped string id's 249 | ---- 250 | switch($var2) { 251 | case 0: 252 | System.out.println("aaa"); 253 | break; 254 | case 1: 255 | System.out.println("bbb"); 256 | break; 257 | case 2: 258 | System.out.println("ccc"); 259 | break; 260 | default: 261 | System.out.println("other"); 262 | } 263 | ---- 264 | 265 | === Switching over string labels with non-unique hash codes 266 | 267 | Hash collisions between distinct string labels require additional equality checks to be performed against possible string values. The following example is intended to showcase this behavior by using the string labels `"Ea"` and `"FB"`, that, despite having distinct values, share a single hash code. 268 | 269 | [source,java] 270 | .Original code 271 | ---- 272 | switch (a) { 273 | case "aaa": 274 | System.out.println("aaa"); 275 | break; 276 | case "FB": 277 | System.out.println("FB"); 278 | break; 279 | case "Ea": 280 | System.out.println("Ea"); 281 | break; 282 | } 283 | ---- 284 | 285 | The code generated for mapping the above labels into unique identifiers will contain equality checks for `"Ea"` and `"FB"` under the same hash label: 286 | 287 | [source,java] 288 | .Compiled code, mapping non-unique hashes to id's 289 | ---- 290 | byte $var2 = -1; 291 | switch(a.hashCode()) { 292 | case 2236: 293 | if (a.equals("Ea")) { 294 | $var2 = 2; 295 | } else if (a.equals("FB")) { 296 | $var2 = 1; 297 | } 298 | break; 299 | case 96321: 300 | if (a.equals("aaa")) { 301 | $var2 = 0; 302 | } 303 | } 304 | ---- 305 | 306 | == Implementing java switches over Enum values 307 | 308 | Switching over Enum values present a similar problem to using Strings: internally switch statements can only operate on numeric data. To solve this problem, switch statements have to rely on the numeric ordinal value of an Enum object, provided by the `int ordinal()` method. Unfortunately, the ordinal values are only part of the runtime state of enum constant objects and, as such, are not available during the compilation process. To overcome this limitation, the Java compiler needs to generate additional lookup structures in the form of static inner classes that, when loaded, can bootstrap themselves with the ordinal values available at runtime. To demonstrate this, consider the following Enum class and switch statement as an example. 309 | 310 | [source,java] 311 | .Enum class 312 | ---- 313 | public enum SomeEnum { 314 | ONE, TWO, THREE 315 | } 316 | ---- 317 | 318 | [source,java] 319 | .Original switch 320 | ---- 321 | switch (a) { 322 | case ONE: 323 | System.out.println("one"); 324 | break; 325 | case TWO: 326 | System.out.println("two"); 327 | break; 328 | case THREE: 329 | System.out.println("three"); 330 | break; 331 | default: 332 | System.out.println("other"); 333 | } 334 | ---- 335 | 336 | Assuming that, the above `switch` statement is used in a class named "Outer", the generated helper class would look something like the following: 337 | 338 | [source,java] 339 | .Generated helper class with enum lookup table 340 | ---- 341 | static class Outer$1 { 342 | 343 | static final int[] $SwitchMap$SomeEnum; 344 | 345 | static { 346 | $SwitchMap$SomeEnum = new int[SomeEnum.values().length]; 347 | $SwitchMap$SomeEnum[SomeEnum.ONE.ordinal()] = 1; 348 | $SwitchMap$SomeEnum[SomeEnum.TWO.ordinal()] = 2; 349 | $SwitchMap$SomeEnum[SomeEnum.THREE.ordinal()] = 3; 350 | } 351 | } 352 | ---- 353 | 354 | For each `Enum` used in switch statements of a class a single `static final int[]` field is generated to act as the lookup table of the type. The table is populated by the static constructor of the helper class to map `Enum` constant ordinals to compiler-assigned numeric case identifiers. Enum constants not used in the switch statement are conveniently assigned the `0` case identifier and thus require no explicit initialization. With the help of the generated lookup table, the original switch can be compiled into as switch over numeric case labels. 355 | 356 | [source,java] 357 | .Compiled switch, branching over numeric values 358 | ---- 359 | switch(Outer$1.$SwitchMap$SomeEnum[a.ordinal()]) { 360 | case 1: 361 | System.out.println("one"); 362 | break; 363 | case 2: 364 | System.out.println("two"); 365 | break; 366 | case 3: 367 | System.out.println("three"); 368 | break; 369 | default: 370 | System.out.println("other"); 371 | } 372 | ---- 373 | --------------------------------------------------------------------------------