Wednesday, October 10, 2018

Book Review: Java by Comparison

I accepted the invitation to review Java by Comparison: Become a Java Craftsman in 70 Examples (2018, The Pragmatic Bookshelf) because the premise of this book interested me. The book Software Craftsmanship: The New Imperative had a significant effect on me when I was a much less experienced software developer and I looked forward to reviewing Java by Comparison because of its connection with the concept of software craftsmanship and its examples being provided in the Java programming language. I was provided with the electronic version of the book and I chose the PDF format for my review.

Java by Comparison is written by Simon Harrer, Jörg Lenhard, and Linus Dietz and has over 160 pages of substantive content (not counting prefaces, forewords, table of contents, etc.). Java by Comparison features nine chapters and the 70 cases covered span the first eight of those nine chapters.

Java by Comparison uses the same format for eight of its nine chapters. Each of these eight chapters are divided into several items each and at least two code samples are presented for each item. The first code sample for each item is typically functionally correct code, but the second code sample for each item illustrates how that particular item's principle can be applied to improve that code. There are three samples of these items ("extracts") available on the book's web site to see what I'm attempting to explain here.

Java by Comparison covers specific principles that fall into the areas of clean code, stylistic considerations, readability considerations, comments, naming, exception handling, unit testing, object-oriented design, and using Java's functional capabilities that were largely introduced to the language with JDK 8. Simple code examples are used in each item to contrast minimally sufficient code with improved code.

The ninth and final chapter is different from the first eight chapters. That chapter has fewer code samples and more descriptive text of general practices experienced Java developers tend to adopt in areas such as static code analysis, build automation, continuous integration, logging, and working with concurrency. This chapter is mostly higher-level and provides a taste of these topics for the newer Java developer.

I consider the following to be strengths of Java by Comparison:

  • Content
    • Java by Comparison collects information useful to newer Java developers in a single location.
    • As Java by Comparison covers various topics, it also introduces useful side notes of value to newer Java developers. Here are some examples of this:
      • There is a side note about "Multiple Returns vs. Single Return per Method" in the section on avoiding unnecessary comparisons.
      • There is a side note about "Never Use Floating-Point Arithmetic for Money" in the section on using appropriate tolerance values in unit testing.
    • Java by Comparison uses several different parts of the JDK API to illustrate its points.
    • Most of the lessons discussed in Java by Comparison are ones that I've had to learn the hard way and I found myself agreeing with most of the recommendations (and I've even blogged on some of them).
  • Format
    • The format comparing working code with superior code is effective in illustrating the principle discussed in each item.
    • The authors find a nice balance of small, simple code examples and just enough text to cover the point.
  • Finish
    • Java by Comparison is polished and has very few grammatical errors or typos. This seems to be an increasingly rare characteristic of technical books.
    • Code listings have color-coded syntax and shaded background in the PDF version of the book.
    • PDF version provides links between pages that reference each other.
    • Links to online resources work from PDF.
    • An accessible errata is available.
  • Tone
    • The text in Java by Comparison is easy to read, highly approachable, succinct, and well suited for those newer to Java.
    • Java by Comparison makes recommendations, but is not overly prescriptive and on several occasions highlights that sometimes "best" is context-sensitive and can only be determined after trying different approaches.
    • Although written by three different authors, the writing of Java by Comparison is consistent and uniform.
  • References
    • Java by Comparison provides frequent references to books and online resources for additional details regarding the concepts it covers.
    • Java by Comparison's "Bibliography" lists several of the most influential books and posts in Java and software development.

Intended Audience

I would have liked to have had access to Java by Comparison when I was still relatively new to Java. I remember reading the first edition of Effective Java and learning some from it, but Java by Comparison would have been more appropriate for my level of comfort with Java at that time.

The "Acknowledgements" section of Java by Comparison provides insight into the intended audience. The authors started building "a collection of common issues" that students in their "Advanced Java Programming" class encountered. The "Who Should Read This Book" section of the book addresses the audience more directly, "This book is for people who are learning to program in Java at a beginner or intermediate level. It's also a classroom resource for teachers who coach new developers in their journey to become programmers." The authors add, "You should read this book after you’ve learned the basic Java syntax - after you're able to write small programs with conditions and loops and you know the basics of object-oriented programming." The authors even provide a FizzBuzz-based assessment for potential readers to determine if they have the minimum recommended Java ability to make this book useful to them.

I agree with the authors' recommendations regarding who will benefit most from Java by Comparison. However, I would add that even more experienced Java developers might benefit greatly from reading Java by Comparison if they have not spent much time reading things such as Effective Java, Clean Code, Holub on Patterns, or other books or online media regarding writing Java more effectively.

My Recommendation

Many developers new to Java who have started feeling comfortable with its syntax and basic APIs often ask what is the next best book from them to read. Java by Comparison now gives me an easy answer to that question. I will recommend this book to developers new to Java as an appropriate book for them to read before moving onto Clean Code and Effective Java.

Java by Comparison is also recommended for more experienced Java developers who have concentrated mostly on getting the job done in the past and would like to do a better job at writing code that is more than sufficient and is easier for the next developer who has to work with it.

Java by Comparison applies a novel approach to presenting ideas for beginning and intermediate Java developers to learn techniques they can practice to become better Java developers and to write better code that will benefit others and themselves.

Additional References

There are three sample "excerpts" available to give a prospective reader an opportunity to determine if he or she likes the format and style of writing. There are listed below. I have also included links to others' reviews of Java by Comparison because we all have different tastes and experiences and it might be helpful for prospective readers of Java by Comparison to read what others have said about the book.

Thursday, October 4, 2018

JDK 12 News (4 October 2018)

There has been significant news related to JDK 12 in recent days.

JDK 12 Early Access Build 14

JDK 12 Early Access Build 14 (2018/10/4) was released today. The release notes indicate that the fix for JDK-8210692 ["The 'com.sun.awt.SecurityWarning' class can be dropped"] is a significant part of this build. The class com.sun.awt.SecurityWarning, deprecated for removal with JDK 11, has now been removed altogether. Other links of interest related to OpenJDK 12 Early Access Build 12 include "Changes in this build" and "Issues addressed in this build". This build also includes, of course, the preview features switch expressions (JEP 325) and raw string literals (JEP 326) that I have blogged on previously.

JEP 340 Targeted for JDK 12

Mark Reinhold announced in the post "JEP proposed to target JDK 12: 340: One AArch64 Port, Not Two" that JEP 340 ["One AArch64 Port, Not Two"] is now targeted for JDK 12.

JEP 341 Targeted for JDK 12

Reinhold also announced in "JEP proposed to target JDK 12: 341: Default CDS Archives" that JEP 341 ["Default CDS Archives"] is also targeted for JDK 12.

JDK 12 Proposed Schedule Approved

In a third consecutive message on the OpenJDK jdk-dev mailing list, Reinhold announced in the message "Proposed schedule for JDK 12" that the proposed schedule for JDK 12 has been approved. General Availability of JDK 12 is currently planned for 19 March 2019. As of this writing, there are now four JEPs targeted for JDK: JEP 325, JEP 326, JEP 340, and JEP 341.

Thursday, September 27, 2018

A Tale of Two Oracle JDKs

There has been concern recently that Java developers will inadvertently use the wrong Oracle-provided JDK implementation now (as of JDK 11) that Oracle provides builds of the open source OpenJDK and also provides commercial JDK builds based largely on the OpenJDK source.

The table below compares and contrasts the two versions of JDK that Oracle provides (but Oracle won't be the only supplier of JDK builds available for free and/or for support charge). Please keep in mind this represents my best personal understanding of the differences and similarities of Oracle's two offerings; please check with an authoritative source before making decisions regarding which Oracle JDK implementation to use (or even whether to use an Oracle implementation).

JDK Builds from Oracle (https://jdk.java.net/)
Characteristic Oracle OpenJDK Builds Oracle JDK (Java SE Downloads)
Oracle's Descriptions "End users and developers looking for free JDK versions: Oracle OpenJDK offers the same features and performance as Oracle JDK under the GPL license." "Oracle Customers and ISVs targeting Oracle LTS releases: Oracle JDK is Oracle's supported Java SE version for customers and for developing, testing, prototyping or demonstrating your Java applications."
Web Address https://jdk.java.net/11/ https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html
License GNU General Public License, version 2, with the Classpath Exception Oracle Technology Network License Agreement for Oracle Java SE
Build Platforms (Binaries) Linux / x64 (tar.gz)
macOS / x64 (tar.gz)
Windows / x64 (zip)
Linux / x64 (tar.gz, deb, rpm)
macOS / x64 (tar.gz, dmg)
Windows / x64 (zip, exe)
Solaris SPARC (tar.gz)
Pay for Production Use No Yes
Oracle Support Select bug fixes and security patches until next JDK version's General Availability release Java SE Subscription
(Support for LTS versions for up to 8 years)
Several Other Paid Support Offerings under "Oracle Customers"
java -version Example openjdk version "11" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)
Required to Accept License Agreement No Yes
Java Flight Recorder Yes Yes
Java Mission Control Yes Yes
Advanced Management Console No Yes
This table represents my personal understanding only; refer to Oracle documentation and OpenJDK documentation for more authoritative information (see "References" below).

There are other implementations of the JDK that will be available as well, with some being free and some requiring payment. I did not discuss those alternatively provided JDKs in this post in order to keep the comparison cleaner and simpler between the "Oracle OpenJDK builds" and the "Oracle JDK builds".

References

Tuesday, September 25, 2018

JDK 11 General Availability

As scheduled, it was announced today that JDK 11 was released for General Availability. Earlier this week, Iris Clark announced the "JSR 384 (Java SE 11) Final Release" and in that same message referenced the final release version of JSR 384, referenced the "Java SE 11 (18.9) Platform JSR (384)" specification page, and concluded, "The 384 EG is now disbanded."

The "JDK 11 General-Availability Release" page provides "production-ready open-source builds of the Java Development Kit, version 11, an implementation of the Java SE 11 Platform under the GNU General Public License, version 2, with the Classpath Exception." That same JDK 11 GA Release page also points to "commercial builds of JDK 11 from Oracle under a non-open-source license" that are "available for a wider range of platforms" and which "can be found at the Oracle Help Center."

The "JDK 11 GA Release" page also provides links to the detailed JDK 11 Release Notes, to the list of features (JEPs) associated with JDK 11, to the Java SE/JDK Version 11 API Specification (Javadoc), and to the Java SE 11 Tools and Command Reference.

In the message "Java 11 / JDK 11: General Availability", Mark Reinhold writes:

JDK 11, the reference implementation of Java 11 and the first long-term support release produced under the six-month rapid-cadence release model[1][2], is now Generally Available. We've identified no P1 bugs since we promoted build 28 over four weeks ago so that’s the official GA release, ready for production use.

JDK 11 is significant for several reasons, not the least of which is its status as the basis for Oracle's LTS offering and the likelihood of this being the version of Java many shops will move to if they are currently on Java 8. An interesting read along these lines for those who do not intend to purchase commercial support for Oracle JDK implementations is "The future of Java and OpenJDK updates without Oracle support." Other recent posts that are probably worth reading in light of JDK 11's release are "Oracle JDK Releases for Java 11 and Later", "Java Is Still Free", and "Introducing Java SE 11".

Oracle's "Java SE Development Kit 11 Downloads" page highlights (in orange-ish background and with emphasized title "Important changes in Oracle JDK 11 License") the "substantially different" Oracle Technology Network License Agreement that now applies to Oracle Java SE and provides a link to "this software under the GPL License on jdk.java.net/11" (Oracle's OpenJDK builds).

Monday, September 24, 2018

Raw String Literals Support in JDK 12 Early Access Build 12

The biggest news this week in the world of Java is likely going to be the General Availability of JDK 11. However, another exciting development is the release of JDK 12 Early Access Build 12 (20 September 2018). This Early Access Build 12 of JDK 12 is significant because it includes implementations related to JEP 326 ["Raw String Literals (Preview)"] via changesets such as changeset 51713:d424675a9743 (JDK-8206981), changeset 51714:975d3636a2f9 (JDK-8200434), and changeset 51725:ccea318862ae (JDK-8210674).

JEP 326 itself displays multiple examples of how raw string literals might be applied in Java code in common situations (file paths, multi-line, regular expressions, database/SQL, and polyglot). These examples also appear in an arguably more readable format in associated JDK-8196004. These "Raw String Literals" examples can be pasted into Java classes/methods and compiled successfully against JDK 12 Early Access Build 12. For convenience, I've placed slightly adapted versions of these in a single Java class on GitHub.

Note that JEP 326 is a "Preview Feature", so you must compile with the javac options --enable-preview and --release 12 or else you'll encounter the error message, "error: raw string literals are a preview feature and are disabled by default" with a pointer to the backtick used to demarcate the raw string literal. Similarly, the code must be run with java launcher option --enable-preview to run successfully and to avoid the error message, "... UnsupportedClassVersionError: Preview features are not enabled ..."

As far as I can determine, Early Access Build 12 does not include an implementation for JDK-8202442 ["String::unescape"]. Some of the library methods added to the String class related to raw string literals are part of JDK 11 and I discussed some of these in the post "Applying New JDK 11 String Methods".

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."

Jesper Wilhelmsson's post spells out some examples where this "timewasters" label might be used. Here are some bugs from the JBS that may have been or may be appropriate for such a label given the text in the bug.

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

Friday, September 7, 2018

Playing with JDK 12's Switch Expressions

In the blog post "JDK Language Feature Preview in Action: Switch Expressions," I discussed how JEP 325 ["Switch Expressions (Preview)"] is an early application of a designated "preview language feature" as spelled out by JEP 12 ["Preview Language and VM Features"]. JEP 325 is targeted for JDK 12. Even better, the Early Access Builds for JDK 12 already provide support for this preview language feature!

I've been excited about the possibilities associated with switch expressions as demonstrated in my previous blog posts "Switch Expressions Coming to Java?", "Enhancing Java switch Statement with Introduction of switch Expression", and the aforementioned "JDK Language Feature Preview in Action: Switch Expressions." The JDK 12 Early-Access Builds make it really easy to start experimenting with this preview language feature and I demonstrate this using JDK 12 Early Access Build 10 (2018/9/6) [latest build as of this writing].

Rather than expending any extra effort in coming up with my own creative example of a switch expression, I'm going to borrow one of the examples provided currently in the JEP 325 itself (early in the "Description" section). This example is really an enhanced switch statement rather than a switch expression, but it requires the same language preview support and is part of that feature. The code listing for that adapted example is shown next.

package dustin.examples.jdk12.switchexp;

public class SwitchExpressionsDemo
{
   /**
    * This example is adopted from the JEP 325 text
    * (http://openjdk.java.net/jeps/325).
    *
    * @param k Value to be switched upon.
    */
   static void howMany(final int k)
   {
      switch (k)
      {
         case 1 -> System.out.println("one");
         case 2 -> System.out.println("two");
         case 3 -> System.out.println("many");
      }
   }

   /**
    * Execute demonstration discussed in JEP 325 using custom
    * method {@link #howMany(int)}.
    */
   public static void demonstrateHowMany()
   {
      howMany(1);
      howMany(2);
      howMany(3);
   }

   public static void main(final String[] arguments)
   {
      demonstrateHowMany();
   }
}

The version of this class shown above can be found on GitHub.

When I attempt to compile the above code with the JDK 12 javac compiler without any special flags, I see the error message: "error: switch rules are a preview feature and are disabled by default." That error message also provides the helpful hint, "(use --enable-preview to enable switch rules)".

Passing the --enable-preview flag to the JDK 12 javac compiler gets me past the error just shown, but then I'm reminded that the --enable-preview flag should be used in conjunction with either the -source flag or the --release flag. The error message clearly states, "error: --enable-preview must be used with either -source or --release".

I elected to go with the --release 12 flag used in conjunction with --enable-preview and that allows the enhanced switch statement demonstration example to compile.

The enhanced switch statement example compiles with the --enable-preview and --release 12 options passed to the JDK 12 javac compiler, but as the output in the last screen snapshot indicates, there is a message that states that the compiled class "uses preview language features" and advises, "Recompile with -Xlint:preview for details." The next screen snapshot demonstrates the results of specifying -Xlint:preview:

Passing the -Xlint:preview flag to the JDK 12 javac compiler when compiling the switch expressions demonstration example led to three warning messages pointing to the specific lines of code using the switch expression (-> syntax) and stating, "warning: [preview] switch rules are a preview feature and may be removed in a future release."

With the enhanced switch statement code compiling, it's now time to try running the code. If the JDK 12 java launcher is invoked without any flags indicating use of the preview feature, an error message is presented: "java.lang.UnsupportedClassVersionError: Preview features are not enabled for ... Try running with '--enable-preview'."

When --enable-preview is passed to the JDK 12 java launcher, the demonstration runs successfully as depicted in the next screen snapshot.

When the output shown in the the last screen snapshot is compared to the source code of the enhanced switch statement demonstration spelled out in the beginning of this post, some observations can quickly be made. Perhaps most noticeable is that there were no break statements in the example, but no fall-through occurred despite the absence of break statements. This is exactly what JEP 325 states should happen for switch expressions and for enhanced switch statements: "If a label is matched, then only the expression or statement to the right of an arrow label is executed; there is no fall through." The presence of the "arrow" (->) indicates we're using a switch expression or enhanced switch statement where the expression or statement will be completely resolved by what lies to the right of the "arrow" (and so no explicit break is required). The presence of the "colon" (instead of the "arrow") with an associated break in a switch expression or enhanced switch statement allows for multiple lines to be specified.

There is much more to see and try related to switch expressions and enhanced switch statements. However, this post has focused on introducing the availability of switch expression and enhanced switch statement support in JDK 12 early access builds and has provided a simple example of that support. It's exciting to start playing with Java switch expressions and enhanced switch statements! It's also exciting to think about a world without switch fallthrough! This will only be a "preview" feature for JDK 12, but I'm looking forward to the beginning of the end for switch fallthrough.