Wednesday, September 19, 2018

JDK Bug System Time Wasters

Several possibilities of the message's possible contents crossed my mind when I saw the title of Jesper Wilhelmsson's message "Introducing time wasters" on the OpenJDK jdk-dev mailing list. In the second or so between reading that link and having the message appear after clicking on the link, I wondered if the message would be about one of the following topics:

  • People wasting the time of developers working on the JDK
  • People wasting the time of developers sharing ideas and responding to questions on the mailing lists
  • Trivial defect reports or reports of observations that are intentional (not defects)

It turns out that Wilhelmsson's topic was more interesting than those. Wilhelmsson opens the message with these two sentences (I added the emphasis), "As an experiment we are introducing a new label in JBS, timewaster. The label is used to tag bugs that for some reason is wasting engineering time." That message provides additional considerations to be made when deciding whether to label a bug in JDK Bug System (JBS) with the "timewaster" label.

The "Labels" section of the JBS Overview page describes the purpose of JBS labels: "Users can associate one or more labels with an issue. Such labels are often used to manage informal processes and record ad hoc information." Although a given label does not necessarily change the priority of a bug (it communicates importance informally rather than formally), Wilhelmsson points out that the "timewaster" label communicates additional urgency, "A time waster has higher urgency than other bugs."

It will be interesting to see how the "timewaster" experiment works out and how the criteria for determining what is a "timewaster" and what is not develops. I just wish I could label some of the defects I get assigned as time wasters.

Monday, September 17, 2018

Java Subtlety with Arrays of Primitives and Variable Arguments

An interesting question was posed in a comment on the DZone-syndicated version of my recent blog post "Arrays.hashCode() Vs. Objects.hash()". The comment's author set up examples similar to those used in my blog post and showed different results than I saw. I appreciate the comment author taking the time to post this as it brings up a subtle nuance in Java that I think is worth a blog post.

The comment author showed the following valid Java statements:

int[] arr = new int[]{1,2,3,4};
System.out.println(Arrays.hashCode(arr));
System.out.println(Objects.hash(1,2,3,4));
System.out.println(Arrays.hashCode(new Integer[]{new Integer(1),new Integer(2),new Integer(3),new Integer(4)}));
System.out.println(Objects.hash(new Integer(1),new Integer(2),new Integer(3),new Integer(4)));

The author of the comment mentioned that the results from running the code just shown were exactly the same for all four statements. This differed from my examples where the result from calling Arrays.hashCode(int[]) on an array of primitive int values was different than calling Objects.hash(Object...) on that same array of primitive int values.

One response to the original feedback comment accurately pointed out that hash codes generated on different JVMs are not guaranteed to be the same. In fact, the Javadoc comment for the Object.hashCode() method states (I added the emphasis):

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

Having stated all of this, the hash codes calculated for integers will typically be consistent from run to run. It was also interesting that the original commenter's examples' output all had exactly the same values. While I might not expect those values to match my examples' values, it is surprising that all of the examples provided by the commenter had the same answer.

The difference between the examples provided in the feedback comment and my examples comes down to how the commenter's example invoked Objects.hash(Object...) for an array of primitive int values versus how my example invoked Objects.hash(Object...) for an array of primitive int values. In my example, I passed the same local array to all the method calls. The commenter's example passed an explicit array of primitive int values to Arrays.hashCode(int[]), but passed individual int elements to Objects.hash(Object...) instead of passing the array to that latter method. When I add another example to the commenter's set of examples that does pass the array of primitive int values to the Objects.hash(Object...) method, I get a generated hash code that is different than all of the others. That enhanced code is shown next.

final int[] arr = new int[]{1,2,3,4};
out.println("Arrays.hashCode(int[]):              " + Arrays.hashCode(arr));
out.println("Objects.hash(int, int, int, int):    " + Objects.hash(1,2,3,4));
out.println("Objects.hash(int[]):                 " + Objects.hash(arr));
out.println("Objects.hashCode(Object):            " + Objects.hashCode(arr));
out.println("int[].hashCode():                    " + arr.hashCode());
out.println("Arrays.hashCode(Int, Int, Int, Int): " + Arrays.hashCode(new Integer[]{1,2,3,4}));
out.println("Objects.hash(Int, Int, Int, Int):    " + Objects.hash(1,2,3,4));

Running the adapted and enhanced version of the code provided by the commenter leads to this output (with the examples I added highlighted):

Arrays.hashCode(int[]):              955331
Objects.hash(int, int, int, int):    955331
Objects.hash(int[]):                 897913763
Objects.hashCode(Object):            897913732
int[].hashCode():                    897913732
Arrays.hashCode(Int, Int, Int, Int): 955331
Objects.hash(Int, Int, Int, Int):    955331

Comparing the output to the code that generated it quickly shows that Arrays.hashCode(int[]) generates the same hash code value as Objects.hash(Object...) when the elements of the array of int values are passed to that latter method as individual elements. However, we can also see that when the array of primitive int values is passed in its entirety (as a single array instead of as the individual elements of the array), the Objects.hash(Object...) methods generates an entirely different hash code. The other two examples that I added (that are highlighted) are to show what the "direct" hash code is on the array of primitive int values by calling .hashCode() directly on the array or by getting the equivalent result via Objects.hashCode(Object). [It's not a coincidence that the hash code generated by Objects.hash(Object...) for the array of primitive int values is exactly 31 greater than the "direct" hash code generated for the array of primitive int values.]

All of this points to the real issue here: it is typically best to not pass an array of primitives to a method that accepts variable arguments (advertises ellipsis). SonarSource Rules Explorer (Java) provides more details on this in RSPEC-3878. What is particularly relevant in that rule description is the question related to ambiguity, "Is the array supposed to be one object or a collection of objects?"

The answer to the question just posed is that when the array of primitive int values is passed to the variable arguments accepting method Objects.hash(Object...), the entire array is treated as a single Object. In contrast, when an array of reference objects (such as Integer) is passed to that same method, it sees it as the same number of objects being passed to it as elements in the array. This is demonstrated by the next code listing and associated output.

package dustin.examples.hashcodes;

import static java.lang.System.out;

/**
 * Demonstrates the difference in handling of arrays by methods that
 * accept variable arguments (ellipsis) when the arrays have primitive
 * elements and when arrays have reference object elements.
 */
public class ArraysDemos
{
   private static void printEllipsisContents(final Object ... objects)
   {
      out.println("==> Ellipsis Object... - Variable Arguments (" + objects.length + " elements): " + objects.getClass() + " - " + objects);
   }

   private static void printArrayContents(final Object[] objects)
   {
      out.println("==> Array Object[] - Variable Arguments (" + objects.length + " elements): " + objects.getClass() + " - " + objects);
   }

   private static void printArrayContents(final int[] integers)
   {
      out.println("==> Array int[] - Variable Arguments (" + integers.length + " elements): " + integers.getClass() + " - " + integers);
   }

   public static void main(final String[] arguments)
   {
      final int[] primitiveIntegers = ArraysCreator.createArrayOfInts();
      final Integer[] referenceIntegers = ArraysCreator.createArrayOfIntegers();
      out.println("\nint[]");
      printEllipsisContents(primitiveIntegers);
      printArrayContents(primitiveIntegers);
      out.println("\nInteger[]");
      printEllipsisContents(referenceIntegers);
      printArrayContents(referenceIntegers);
   }
}
int[]
==> Ellipsis Object... - Variable Arguments (1 elements): class [Ljava.lang.Object; - [Ljava.lang.Object;@2752f6e2
==> Array int[] - Variable Arguments (10 elements): class [I - [I@1cd072a9

Integer[]
==> Ellipsis Object... - Variable Arguments (10 elements): class [Ljava.lang.Integer; - [Ljava.lang.Integer;@7c75222b
==> Array Object[] - Variable Arguments (10 elements): class [Ljava.lang.Integer; - [Ljava.lang.Integer;@7c75222b

The example code and associated output just shown demonstrate that the method expecting variable arguments sees an array of primitive values passed to it as a single element array. On the other hand, the same method sees an array passed to it with reference object types as being an array with the same number of elements.

Returning to the hash code generation examples with this in mind, the different hash code generated by Objects.hash(Object...) for an array of primitive int values than that generated by Arrays.hashCode(int[]) makes sense. Similarly, we now can explain why the arrays of object references lead to the same hash code regardless of which of those methods is called.

I mentioned earlier that it's not a coincidence that the hash code generated by Objects.hash(Object) is exactly 31 higher than the "direct" hash code of the overall array. This was not surprising because the OpenJDK implementation of Objects.hash(Object...) delegates to Arrays.hashCode(Object[]), which uses 31 as the prime number it multiplies by each element in the calculated hash code. The hash code value provided by Objects.hash(Object...) for an array of primitive int values appears to be exactly what the method's implementation would lead us to expect with the above observations in mind: the direct hash value of the overall array plus the 31 prime number. When that hash code method only loops over a single element (which is the case for an array of primitives passed to a method expecting variable arguments), its calculation is essentially 31 * 1 + <directHashValueOfOverallArray>.

It's worth noting here that even though an array of reference objects had its hash code calculated to the same result as when the elements were passed to the method accepting variable arguments, it is still probably best to avoid passing an array of reference objects to such a method. The javac compiler provides this warning when this occurs: "warning: non-varargs call of varargs method with inexact argument type for last parameter" and adds these useful details about potential ways to address this: "cast to Object for a varargs call" or "cast to Object[] for a non-varargs call and to suppress this warning". Of course, with JDK 8 and later, it's fairly straightforward to process an array in various other ways before providing it to a method expecting variable arguments.

I added a final paragraph to my original post (and its DZone-syndicated version) to attempt to quickly address this, but I have used this post to express this information in greater detail. The specific lessons learned here can be summarized as "Favor the appropriate overloaded Arrays.hashCode method for an array of primitives instead of using Objects.hash(Object...)" and "Favor Arrays.hashCode(Object[]) for arrays of reference types instead of using Objects.hash(Object...)." The more general guidelines are to be wary of passing an array of primitive values to a method expecting variable arguments of type Object if the number of elements the invoked method "sees" is important in any way and to be wary of passing an array of reference objects to a method expecting variable arguments to avoid compiler warnings and the ambiguity being warned about.

Thursday, September 13, 2018

JDK 12 News (13 September 2018)

With General Availability of JDK 11 planned for later this month (25 September 2018), it's a good time to start looking more closely at JDK 12.

In a message titled "Proposed schedule for JDK 12" on the OpenJDK jdk-dev mailing list, Mark Reinhold announced, "With JDK 11 nearly out the door, here's a proposed schedule for JDK 12." The schedule for JDK 12 proposed in that message includes the "Release-Candidate Phase" starting on 31 January 2019 and "General Availability" of JDK 12 on 19 March 2019.

There are already two JEPs targeted for JDK 12 and both of them are "preview features" (my previous blog posts on these JEPs are listed as sub-bullets):

JDK 12 Early Access Build 11 was also released today. Build 11 addresses several minor issues and implements multiple minor changes.

Wednesday, September 12, 2018

Arrays.hashCode(Object[]) versus Objects.hash(Object...)

Since JDK 1.5, the Arrays class has offered overloaded static methods with the name "hashCode"​. Most of the overloaded methods accept an array of a particular primitive type, but the Arrays.hashCode(Object[]) method can be used to calculate an int hash code for an array of reference types. Since its JDK 1.7 inception, the Objects class has provided a method called hash(Object...) that also returns an int hash code for a provided array of Java objects (the ellipsis [...] representing Java varargs is handled as an array and accepts an array). This post provides a brief comparison between Arrays.hashCode(Object) and Objects.hash(Object...).

We can look at the code in OpenJDK to see how OpenJDK implements the two methods being compared here. It turns out that Arrays.hashCode(Object[]) and Objects.hash(Object...) behave exactly the same way because Objects.hash(Object...) completely delegates to Arrays.hashCode(Object[]). This is shown in the next code listing extracted from the OpenJDK Objects.java class.

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}

So, it turns out that the methods are really the same when one or more individual object references are passed. So, for many cases, whichever you choose is mostly a matter of taste. It may appeal to some to use the Arrays method directly given that's what's going to be called anyway. It is typically preferable to use the Arrays method when passing it a construct that is already known to be a Java array and to use the Objects method for situations where the values are being passed in a comma-separated combination without explicit array syntax being required (such as the case of implementing a custom class's hashCode() method and passing that class's attributes of arbitrary types for the hash code computation). When using an array of primitives of the same type, it is typically better to use the appropriate version of Arrays.hashCode for that particular primitive.

The simple class shown in the next code listing (and available on GitHub) demonstrates the differences and similarities in output between the overloaded versions of Arrays.hashCode and the Objects.hash(Object...) method.

package dustin.examples.hashcodes;

import java.util.Arrays;
import java.util.Objects;

import static java.lang.System.out;

/**
 * Demonstration that writes output to standard output with
 * hash codes generated for the same underlying array data by
 * both {@code Arrays.hashCode(Object[])} and by
 * {@code Objects.hash(Object...)}.
 */
public class HashesComparedDemo
{
   public static void main(final String[] arguments)
   {
      final int[] integers = ArraysCreator.createArrayOfInts();
      out.println("Arrays.hashCode(int[]) for int[]: " + Arrays.hashCode(integers));
      out.println("Objects.hash(Object...) for int[]:   " + Objects.hash(integers));
      out.println("Objects.hashCode(Object) for int[]:  " + Objects.hashCode(integers));

      final Integer[] refIntegers = ArraysCreator.createArrayOfIntegers();
      out.println("Arrays.hashCode(Object[]) for Integer[]: " + Arrays.hashCode(refIntegers));
      out.println("Objects.hash(Object...) for Integer[]:   " + Objects.hash(refIntegers));
      out.println("Objects.hashCode(Object) for Integer[]:  " + Objects.hashCode(refIntegers));

      final String[] strings = ArraysCreator.createArrayOfStrings();
      out.println("Arrays.hashCode(Object[]) for String[]: " + Arrays.hashCode(strings));
      out.println("Objects.hash(Object...) for String[]:   " + Objects.hash(strings));
      out.println("Objects.hashCode(Object) for String[]:  " + Objects.hashCode(strings));
   }
}

The code shown above passes three common data sets (an array of primitive int values, an array of reference Integer values, and an array of String values) to the methods Arrays.hashCode, Objects.hash(Object...), and the Objects.hashCode(Object) method that accepts a single Object (of which an overall array qualifies). The simple example then writes the respective hash code values generated by each method for each data set to standard output. The results of running this code are shown next.

Arrays.hashCode(int[]) for int[]: 1722319241
Objects.hash(Object...) for int[]:   356573628
Objects.hashCode(Object) for int[]:  356573597
Arrays.hashCode(Object[]) for Integer[]: 1722319241
Objects.hash(Object...) for Integer[]:   1722319241
Objects.hashCode(Object) for Integer[]:  1735600054
Arrays.hashCode(Object[]) for String[]: 448603921
Objects.hash(Object...) for String[]:   448603921
Objects.hashCode(Object) for String[]:  21685669

As we would expect, Arrays.hashCode(Object[]) and Objects.hash(Object...) return the same calculated hash code for the reference types Integer and String because they both effectively are the implementation of Arrays.hashCode(Object[]). The array of primitive int values leads to different results from Arrays.hashCode(int[]) than from Objects.hash(Object...) and this is, of course, because the array of primitives is passed to an overloaded Arrays.hashCode(int[]) method specifically implemented for that primitive data type rather than to Arrays.hashCode(Object[]).

If one compares the implementation of Arrays.hashCode(int[]) to the implementation of Objects.hash(Object...) delegated to Arrays.hashCode(Object[]), the implementations look essentially the same in terms of logic. The reason for the different result in the case of an array of primitive int values passed to Arrays.hashCode(int[]) when compared to the result from passing that same array of primitive int values to the Objects.hash(Object...) method is that the entire array of int values is treated as a single Object rather than as an array of individual objects when passed to Objects.hash(Object...). Favor the appropriate overloaded Arrays.hashCode method for an array of primitives instead of using Objects.hash(Object...) and favor Arrays.hashCode(Object[]) for arrays of reference types instead of using Objects.hash(Object...) to avoid compiler warnings and associated ambiguity.

Tuesday, September 11, 2018

JEP 342: The JVM and Spectre

JEP 342 ("Limit Speculative Execution") has transitioned from "Draft" state to "Candidate" state, but it has not yet been targeted to a specific JDK release (see graphic for steps in JEP process). Jesper Wilhelmsson has written that JEP 342 "covers the initial work to make use of new C++ compiler options to limit speculative execution in native code in the JDK." JEP 342's "Summary" succinctly describes its purpose, "Help developers and deployers defend against speculative-execution ('Spectre') vulnerabilities by providing a means to limit speculative execution, and enable further mitigations to be implemented in future releases."

JEP 342 aims to provide a "distinct, alternate JVM in the JDK that is compiled with" C++ "compiler options that limit the extent to which the CPU can do speculative execution" and which "can be selected at launch time." The JEP states that the plan is to "include a distinct, alternate JVM in the JDK that is compiled with these options and can be selected at launch time." The JEP 342 text describes the specific C++ compilers and options affected by this JEP:

JEP 342 describes the default execution of the java launcher to continue to be as it is today (nothing to avoid speculative execution), but with the availability of a command-line flag [-nonspeculative] to disable speculative execution. The decision was made to avoid always prohibiting speculative execution because it should "be up to the end user to decide whether to limit speculative execution." The ability to choose is important because, as the JEP states, the impact of "limiting speculative execution ... can be significant for the C and C++ code in the HotSpot JVM, but it is negligible for the C and C++ code outside of the JVM." It is worth noting here that the "non-JVM code" will always be compiled with the new C++ options addressing speculative execution given that there is very little performance impact in those areas.

Spectre has been one of multiple threats to software applications and it's nice to see new possibilities for dealing with these threats potentially coming to the JVM.

JDK 12 Switch Expression Encountering Unanticipated Enum Value

As I wrote about in my post "Playing with JDK 12's Switch Expressions", the JDK 12 Early Access Builds have made it easy to experiment with the implementation of JEP 325 ["Switch Expressions (Preview)"]. My post "JDK 12: Switch Statements/Expressions in Action" used code examples to demonstrate core features and characteristics of the enhanced switch statement and the new switch expression. In this post, I look at a special case explicitly called out in JEP 325 related to a runtime addition to an enum used in a switch expression.

Because a switch expression returns a value, it is necessary that all possible cases the switch might encounter are handled via a case (or covered by a default for those not explicitly associated with a case). JEP 325 states the following:

The cases of a switch expression must be exhaustive; for any possible value there must be a matching switch label. In practice this normally means simply that a default clause is required; however, in the case of an enum switch expression that covers all known cases (and eventually, switch expressions over sealed types), a default clause can be inserted by the compiler that indicates that the enum definition has changed between compile-time and runtime. (This is what developers do by hand today, but having the compiler insert it is both less intrusive and likely to have a more descriptive error message than the ones written by hand.)

I have written code similar to that described in JEP 325 ("this is what developers do by hand today") as discussed in my blog post "Log Unexpected Switch Options." In the past, it was often wise to add logic for handling or logging switch statement options that were not explicitly called out or handled in a default. With the advent of switch expressions via JDK 12 and JEP 325, it is now required.

JEP 325 addresses the case of a switch expression on an enum and explicitly specifies how to support situations when all of an enum's values were explicitly specified in case clauses when the enum and code with the switch expression were compiled, but then more values were later added to the enum without recompiling the switch expression code using that enum.

To demonstrate this support, I will present a simple enum along with two examples based on JEP 325 and the JDK Early Access Build 10 to use that enum in a switch statement and a switch expression.

The following code listing shows a simple enum called Response that only has two values.

package dustin.examples.jdk12.switchexp;

/**
 * Enum representation of a response.
 */
public enum Response
{
   YES,
   NO;
}

The next code listing shows a class that includes two methods that use the enum shown above. One method uses a switch statement against that enum and the other uses a switch expression against that enum.

package dustin.examples.jdk12.switchexp;

import static java.lang.System.out;

/**
 * Demonstrates implicit handling of expanding enum
 * definition related to JEP 325 switch expressions and
 * switch statements.
 */
public class GrowingEnumSwitchDemo
{
   public static void printResponseStringFromStatement(final Response response)
   {
      out.println("Statement [" + response.name() + "]:");
      switch (response)
      {
         case YES:
            out.println("Si!");
            break;
         case NO:
            out.println("No!");
            break;
      }
   }

   public static void printResponseStringFromExpression(final Response response)
   {
      out.println("Expression [" + response.name() + "]:");
      out.println(
         switch (response)
         {
            case YES -> "Si!";
            case NO -> "No!";
         });
   }

   public static void main(final String[] arguments)
   {
      if (arguments.length < 1)
      {
         out.println("Provide an appropriate 'dustin.examples.jdk12.switchexp.Response' string as an argument.");
         System.exit(-1);
      }
      final String responseString = arguments[0];
      out.println("Processing string '" + responseString + "'.");
      final Response response = Response.valueOf(responseString);
      printResponseStringFromStatement(response);
      printResponseStringFromExpression(response);
   }
}

The code above (which is also available on GitHub) will compile without incident and when I execute the main function on the GrowingEnumSwitchDemo class and pass it the "YES" string, it works as expected. If I add a new value MAYBE to the Response enum and compile only that enum Java file and then run the GrowingEnumSwitchDemo.main(String[]) with string "MAYBE", I encounter an IncompatibleClassChangeError. The new Response.java listing is shown next, followed by a screen snapshot that demonstrates the issue just described once the enum only was re-compiled with new value and run with the previously compiled calling code.

package dustin.examples.jdk12.switchexp;

/**
 * Enum representation of a response.
 */
public enum Response
{
   YES,
   NO,
   MAYBE;
}

The presence of the IncompatibleClassChangeError makes it obvious immediately that there is a new value on the enum not previously handed by the switch expression. This allows the developer to fix the switch expression either by adding a case for the enum value or by adding a catch-all default. This is likely to be better than the current situation today where a switch statement using the :/break syntax will silently move on without exception message in the same situation (which is also demonstrated in the previous code listing and screen snapshot).

There are several things to like about the enhancements coming to Java via JEP 325. The "arrow" syntax allows switch expressions and switch statements to not be burdened with surprising scope issues, risk of unintentional fall-through, or need for explicit breaks. Furthermore, switch expressions, which must return a value, can be used in conjunction with enums to ensure that all enum values are always handled at compile-time (won't compile if all enum values are not handled at compile-time) or that an error is thrown if the enum being used has a value added to it and is used with the previously compiled client code.

Monday, September 10, 2018

JDK 12: Switch Statements/Expressions in Action

My last post "Playing with JDK 12's Switch Expressions" talked about use of the JDK 12 Early Access Builds to try out JEP 325 switch expressions and statements and provided a simple example. This post uses the same JDK 12 Early Access Build 10 to demonstrate different features of switch expressions and enhanced switch statements.

I used a 2x2 grid in the blog post "Enhancing Java switch Statement with Introduction of switch Expression" to illustrate that the new "arrow" syntax ("switch labeled rule") can be used with a switch statement or with a switch expression per JEP 325. Similarly, per JEP 325, the traditional "colon" syntax ("switch labeled statement group") can also be used with either a switch expression or with a switch statement. In other words, presence of the colon (:) does NOT necessarily imply a switch statement and presence of an "arrow" (->) does NOT necessarily imply a switch expression. For convenience, I've included an adapted version of the table shown in my earlier post here.

  STATEMENT
("Nonlocal control flow _out_ of a switch [continue to an enclosing loop, break with label, return]")
EXPRESSION
(Totality: return a value)
SWITCH LABELED STATEMENT GROUP
("Colon")
(Enables Fall-through)
switch we know and "love", but enhanced break returns a value like return
SWITCH LABELED RULE
("Arrow")
(Prevents Fall-through)
"Syntactic shorthand" for Statement/Colon (above) plus
  • "obviates the annoyance of 'break'"
  • "implicitly prevents fallthrough of all forms"
  • "avoids the confusion of current switch scoping"
Arrow (->) points to returned value

With the JDK 12 Early Access Builds, it is convenient to try the new switch expression out and we can also try out the traditional and enhanced versions of the switch statement.

Traditional switch Statement

The traditional switch statement that we "know and love" is still available even with JDK 12 preview enabled (--enable-preview). An example of this traditional switch statement that compiles and executes successfully even with JDK 12 language feature preview enabled is shown next.

/**
 * Demonstrate traditional switch statement assigned to
 * local variable.
 */
public static void demonstrateTraditionalSwitchStatement()
{
   out.println("Traditional Switch Statement:");
   final int integer = 3;
   String numericString;
   switch (integer)
   {
      case 1 :
         numericString = "one";
         break;
      case 2 :
         numericString = "two";
         break;
      case 3:
         numericString = "three";
         break;
      default:
         numericString = "N/A";
   }
   out.println("\t" + integer + " ==> " + numericString);
}

This and all other code examples shown in this post are available on GitHub. This particular example shows a common use of the traditional switch statement to set a local variable's value. I intentionally chose this use case because a new switch expression is an improved approach to accomplishing this.

Enhanced switch Statement

As stated previously, we can use the new "arrow" syntax ("switch labeled rules") with the enhanced switch statement. This is shown in the next code example that compiles and runs against the JDK 12 Early Access Build 10 when --enalved-preview is used.

/**
 * Demonstrate enhanced switch statement used to assign
 * a local variable.
 */
public static void demonstrateEnhancedSwitchStatement()
{
   out.println("Enhanced Switch Statement:");
   final int integer = 2;
   String numericString;
   switch (integer)
   {
      case 1 -> numericString = "one";
      case 2 -> numericString = "two";
      case 3 -> numericString = "three";
      default -> numericString = "N/A";
   }
   out.println("\t" + integer + " ==> " + numericString);
}

This last examples shows the switch still being used as a statement, but in this case it takes advantage of the "arrow" syntax ("label rules") to accomplish its switching without explicit specification of break. This is not only less code, but more importantly has the advantage of not allowing for the often dreaded switch "fall-through." In short, the enhanced switch statement works like the current/traditional switch statement, but without the potential warts of the traditional version.

New switch Expression Returning Value via break

Beyond enhancing the current switch statement to allow for specification of a switch statement without risk of fall-through, JEP 325 also introduces the concept of using the switch keyword in a switch expression. The Java Tutorial's "Expressions, Statements, and Blocks" page explains the differences between statements and operations. For purposes of this discussion, two of the important observations made in that tutorial are (I added bold emphasis):

  1. "An expression is a construct made up of variables, operators, and method invocations ... that evaluates to a single value."
  2. "The Java programming language allows you to construct compound expressions from various smaller expressions as long as the data type required by one part of the expression matches the data type of the other."

The next code listing demonstrates how, with JDK 12 Early Access Build 10 and --enable-preview, one can replace the code shown above that used a switch statement to assign a value to an earlier declared local variable with a single statement that uses a switch expression to assign its result value to the local variable in a single statement.

/**
 * Demonstrate switch expression using colons and breaks.
 */
public static void demonstrateSwitchExpressionWithBreaks()
{
   final int integer = 1;
   out.println("Switch Expression with Colons/Breaks:");
   final String numericString =
      switch (integer)
      {
         case 1 :
            break "uno";
         case 2 :
            break "dos";
         case 3 :
            break "tres";
         default :
            break "N/A";
      };
   out.println("\t" + integer + " ==> " + numericString);
}

The code example just shown demonstrates use of a switch expression that looks very similar to the traditional switch statement example shown earlier. However, there are a couple significant differences. One difference is that this switch expression returns a result that is assigned to the local variable "numericString". The second difference, which directly relates to the switch expression being able to return a value, is that the break clauses now each have the value to be returned for the relevant case immediately specified after the break keyword. In essence, the break in the switch expression acts like a Java method return.

New switch Expression Returning Value via Label Rules

The example just shown demonstrates that one can return a value from a switch expression with similar colon (:) and break syntax to what one is likely used with switch statements. Besides being familiar, the other advantage of this is that one can specify multiple statements to occur for a single case before returning a single value. In most cases, however, it will likely become popular to return a value from a switch expression using the "arrow" syntax discussed earlier to benefit from no risk of fall-through and to avoid scope surprises commonly associated with the traditional switch statement. The next code listing demonstrates how the new switch expression can use "label rules" ("arrow" syntax) instead of colon and break to elegantly return a single resolved value for the switch.

/**
 * Demonstrate switch expressions using "arrow" syntax.
 */
public static void demonstrateSwitchExpressionWithArrows()
{
   final int integer = 4;
   out.println("Switch Expression with Arrows:");
   final String numericString =
      switch (integer)
      {
         case 1 -> "uno";
         case 2 -> "dos";
         case 3 -> "tres";
         case 4 -> "quatro";
         default -> "N/A";
      };
   out.println("\t" + integer + " ==> " + numericString);
}

The four examples above demonstrate each of the cases shown in the 2x2 grid. The remainder of this post will discuss some additional observations from trying out switch expressions and statements with JDK 12 Early Access Build 10.

Multiple Constants Can Be Specified for a Single case

Any of the four quadrants in the 2x2 grid allow for multiple constants to be associated with a single case. This is demonstrated in the next code listing that compiles and runs with JDK 12 Early Access Build 10 with "preview language features" enabled.

/**
 * Demonstrate that multiple constants can be associated with
 * a single {@code case} and used in conjunction with a
 * {@code switch} expression that uses the "arrow" syntax.
 */
public static void demonstrateLabelRulesWithSharedCases()
{
   final int integer = 7;
   out.println("Multiple Case Labels:");
   final String numericString =
      switch (integer)
      {
         case 0 -> "zero";
         case 1, 3, 5, 7, 9 -> "odd";
         case 2, 4, 6, 8, 10 -> "even";
         default -> "N/A";
      };
   out.println("\t" + integer + " ==> " + numericString);
}

/**
 * Demonstrate that multiple constants can be associated with
 * a single {@code case} and used in conjunction with a
 * {@code switch} statement that uses the traditional colon and
 * {@code break} syntax.
 */
public static void demonstrateBlockedStatementsWithSharedCases()
{
   final int integer = 6;
   out.println("Multiple Case Labels:");
   String numericString;
   switch (integer)
   {
      case 0:
         numericString = "zero";
         break;
      case 1, 3, 5, 7, 9:
         numericString = "odd";
         break;
      case 2, 4, 6, 8, 10:
         numericString = "even";
         break;
      default:
         numericString = "N/A";
   };
   out.println("\t" + integer + " ==> " + numericString);
}

"Arrow" ("label rules") and Colon/break ("statement group") Cannot Be Mixed

The JDK 12 Early Access Build 10 compiler (javac) does NOT allow the mixing of the "arrow" syntax and the traditional colon/break syntax. Attempting to mix these results in the error message: "error: different kinds used in the switch". An example of code that would not compile and would show this particular error message is shown next.

/**
 * WARNING - This does NOT compile, even with JDK 12 Early
 * Access Builds and --enable-preview because JEP 325 does
 * not allow the "arrow" syntax to be mixed with the
 * traditional colon/break syntax.
 */
public static void demonstrateMixed()
{
   final int integer = 3;
   String numericString;
   switch(integer)
   {
      case 1 :
         numericString = "one";
         break;
      case 2 -> numericString = "two";
      default -> numericString = "N/A";
   }
   return numericString;
}

switch Statement's break Cannot Return Value

The new switch expression returns a value and when the colon and break approach are used by the switch expression, that returned value is designated immediately following the break keyword. Because the traditional switch statement does not return a value, it is a compile-time error to attempt to have a break associated with a switch statement designate a return value. The error ("error: unexpected value break") can be reproduced with the following code.

/**
 * WARNING - This does NOT compile, even with JDK 12 Early
 * Access Builds and --enable-preview because it is
 * nonsensical to have a "statement" return a value; that
 * is what an expression should be used for.
 */
public static void demonstrateSwitchStatementReturnedLabel()
{
   final int integer = 4;
   switch (integer)
   {
      case 1:
         break "one";
      case 2:
         break "two";
      case 3:
         break "three";
      default:
         break "N/A";
   };
}

When one attempts to compile the above code using JDK 12 Early Access Build 10's javac compiler with flags --enable-preview and -release 12 specified, four instances (corresponding to the three case plus one default) of the error message "error: unexpected value break" are seen. Not surprisingly, the simple change of assigning this switch to a local variable (and effectively turning the statement into an expression) allows this code to compile. In other words, changing the code above to the code in the next code listing allows it to compile and run successfully.

/**
 * This demonstrates that a {@code switch} "expression" is
 * able to (and expected to) provide the "return" value for
 * a given {@code case} and {@code default} instead of being
 * a compiler error as it was for the "statement" example
 * demonstrated in method
 * {@link #demonstrateSwitchStatementReturnedLabel()}.
 */
public static void demonstrateSwitchExpressReturnedLabel()
{
   final int integer = 4;
   final String numericString =
   switch (integer)
   {
      case 1:
         break "one";
      case 2:
         break "two";
      case 3:
         break "three";
      default:
         break "N/A";
   };
}

The current JEP 325 text includes a discussion on how this break behavior is similar to methods' return. That discussion points out that the switch statement requiring no returned value after its breaks is analogous to a method returning void. A switch expression is expected to return a non-void value.

switch Statement's "Arrow" Syntax Must Point to a Statement

The following code will not compile with JDK 12 Early Access Build 10 even with --enable-preview and -release 12 provided to the javac compiler.

/**
 * WARNING - This does not compile, even with JDK 12 Early
 * Access Builds and --enable-preview and reports error message
 * "error: not a statement" because it is expecting a
 * {@code switch} "statement" but what is being provided to each
 * {@code case} is NOT a statement.
 */
public static void demonstrateSwitchStatementReturnedValueViaLabelRule()
{
   final int integer = 5;
   switch (integer)
   {
      case 1 -> "one";
      case 2 -> "two";
   };
   out.println(numericString);
}

The above code does not compile and the error message reported is, "error: not a statement". This is because the switch is being used as a statement in this example, but the "arrow" syntax is "pointing" to literal strings rather than to a valid Java statement.

All Possibilities Must be Specified in a switch Expression

Because a switch expression needs to return a non-void value, a switch expression must specify a case for all possible values it might switch on. In practice, this is likely to be accomplished via a default to catch all possibilities not explicitly specified with case. With a traditional switch statement, it was not required to ensure that all possible values being switched on were covered by a case or default and that led sometimes to conditions such as I described in the blog post "Log Unexpected Switch Options".

The following code violates the rule that a switch expression must specify all possible values in either a case or via default:

/**
 * WARNING - This method will not compile even with JDK 12
 * Early Access Build 10 with --enable-preview because of
 * error; "the switch expression does not cover all possible
 * input values".
 */
public static void demonstrateLackingCaseInSwitchExpression()
{
   final int integer = 5;
   String numericString =
      switch (integer)
      {
         case 1 -> "one";
         case 2 -> "two";
      };
   out.println(numericString);
}

The code just shown will not compile and the causal error message is, "error: the switch expression does not cover all possible input values."

The Effect of JEP 325 on Future Use of switch

Considering the possibilities presented by the availability of switch expressions in Java being available in addition to switch statements, and considering the advantages offered by the new "arrow" syntax that can be used with switch expressions or statements, it is interesting to begin thinking about when each quadrant in the above 2x2 grid is most beneficial. In general, I believe I will find myself using the switch expression with "arrow" syntax ("label rules") most often with enhanced switch statements using "arrow" syntax also being frequently used. I suspect I'll use the traditional : (break) syntax far less often in the future. Even when I have multiple statements to be executed for a particular case, I'll likely factor those statements into a single method that can be called in the case using the "arrow" syntax. This will allow me to benefit from more obvious scoping and avoid the risks of fall-through. Given the ability to specify multiple constants for a single case that will now be available, fall-through won't be necessary anymore even in cases where multiple cases lead to the same result.

Additional Resources