Showing posts with label Java 12. Show all posts
Showing posts with label Java 12. Show all posts

Friday, January 31, 2020

Source Code for Effective Java Third Edition Updated to Use Newer Features

Those who have read the Third Edition of Effective Java are likely aware of the source code associated with that book available on GitHub. The jbloch/effective-java-3e-source-code project has 1700+ stars and has been forked nearly 800 times as of this writing. The version of Java featured in the Third Edition of Effective Java is largely JDK 8 with some coverage of JDK 9 (see my earlier post for details on what is covered in this third edition).

Much has been added to the JDK since the publication of the Third Edition of Effective Java and many new releases have arrived with the faster 6-month cadence. Given this, I was particularly interested to see in an amber-spec-experts mailing list post that Rémi Forax has forked jbloch/effective-java-3e-source-code into the GitHub project forax/effective-java-3e-source-code that has "taken the source of Effective Java (3rd Ed) and change them to use var, switch expression, records and the instanceof with the type test pattern."

There are several things that I like about the idea of refreshing examples from Effective Java (Third Edition) to use newer features:

  • Developers can see how to apply effective Java practices using recently released features.
  • Developers can view the differences between the JDK 8/9 versions and the newer versions to see how new constructs replace older constructs and thus gain a better understanding of the newer constructs.
  • It is useful to see some of the changes when deciding whether a particular change to use a newer construct really helps with code readability in a given situation.

The main page for the forked forax/effective-java-3e-source-code (README.md) states, "The source code have been updated to use new constructs available since Java 9, the version used by the 3rd edition." That page then provides bullets on the types of new constructs applied to the source code with links to each new construct's associated JDK Enhancement Proposal (JEP).

As of this writing, Commit 275eef87e4661f7f1edc41f4730cecf7a1096a97 is the main commit of interest. It covers changes to 113 files. I'll call out a few specific changes here to illustrate the types of changes applied (some of which are to apply preferred constructs that were available even before JDK 9):

Conclusion

The ability to view changes to the original source code associated with the Third Edition of Effective Java to accommodate new language constructs is highly useful in terms of learning about the new constructs and how they relate to or replace old constructs and in deciding if the differences are desirable in different situations.

Monday, April 22, 2019

OpenJDK on GitHub

Project Skara was created "to ... investigate alternative SCM and code review options for the JDK source code, including options based upon Git rather than Mercurial, and including options hosted by third parties." The OpenJDK skara-dev mailing list included a post from Robin Westberg last week that announced, "We have added some additional read-only mirrors of a few different OpenJDK project repositories to the https://github.com/openjdk group..."

The read only OpenJDK repositories on GitHub will likely be more convenient for developers wanting to take advantage of the "open source" nature of OpenJDK to take a peek at its internals. More developers are likely to be comfortable with Git than with Mercurial. The GitHub-hosted repositories make it even easier to clone a given repository or to even fork it.

As of this writing, there are currently nine public repositories hosted on the OpenJDK GitHub site:

This is not the first time the OpenJDK has been mirrored on GitHub. There are 11 repositories in "Mirror of OpenJDK repositories": jdk (2017), jdk7u-jdk (2012), jdk7u (2012), openjdk-mirror-meta (2015), corba (2015), jaxp (2015), jdk7u-langtools (2012), jdk7u-jaxws (2012), jdk7u-jaxp (2012), jdk7u-hotspot (2012), and jdk7u-corba (2012). There is also a Project-Skara/jdk that was last updated in August 2018.

Project Skara is not finished and active development of OpenJDK continues on the Mercurial-based version control system. However, the availability of important OpenJDK repositories on GitHub should make it more convenient for Java developers to analyze OpenJDK source code.

Tuesday, March 19, 2019

Java 12 General Availability

Mark Reinhold announced today that "JDK 12, the reference implementation of Java 12, is now Generally Available." In that announcement, Reinhold stated that "GPL-licensed OpenJDK builds from Oracle are available" at https://jdk.java.net/12 and that "builds from other implementors will no doubt be available soon." Reinhold's post also summarized the eight features delivered with JDK 12:

  • 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)
  • 230: Microbenchmark Suite
  • 325: Switch Expressions (Preview)
  • 334: JVM Constants API
  • 340: One AArch64 Port, Not Two
  • 341: Default CDS Archives
  • 344: Abortable Mixed Collections for G1
  • 346: Promptly Return Unused Committed Memory from G1

There have been, of course, several other blog posts and online resources announcing the JDK 12 release. Some of the most significant are:

Here are some of my previous posts related to JDK 12:

Reinhold completed his "Java 12 / JDK 12: General Availability" message, "Coming up next ... lucky 13!" In a slightly later message on that same mailing list, Reinhold provided the "Proposed schedule for JDK 13":

2019/06/13Rampdown Phase One
2019/07/18Rampdown Phase Two
2019/08/08Initial Release Candidate
2019/08/22Final Release Candidate
2019/09/17General Availability

Monday, January 28, 2019

Custom Compact Number Patterns with JDK 12 Compact Number Formatting

The post "Compact Number Formatting Comes to JDK 12" has been the subject of discussion on a java subreddit thread. Concerns expressed in that thread related to presentation of the compact number formatting deal with digits of precision displayed and the compact number patterns displayed. The digits of precision can be addressed via use of CompactNumberFormat.setMinimumFractionDigits(int) and this approach is discussed in more detail in the post "Using Minimum Fractional Digits with JDK 12 Compact Number Formatting." The second issue (dislike for the compact patterns used in the pre-built CompactNumberFormat instances for certain languages) is addressed in this post.

As far as I can determine (and I certainly could be missing something), there's no method on CompactNumberFormat that allows for the compact number patterns to be set on an existing instance of CompactNumberFormat. However, if the constructor for CompactNumberFormat is used to obtain an instance of this class (rather than using one of the overloaded static factory methods on NumberFormat), then the compact number patterns can be supplied via that constructor to the new instance of CompactNumberFormat. This is demonstrated in the next code listing (also available on GitHub).

/**
 * Provides an instance of {@code CompactNumberFormat} that has been
 * custom created via that class's constructor and represents an
 * alternate Germany German representation to that provided by an
 * instance of {@code CompactNumberFormat} obtained via the static
 * factory methods of {@code NumberFormat} for {@code Locale.GERMANY}.
 *
 * @return Instance of {@code CompactNumberFormat} with customized
 *    alternate German compact pattern representations.
 */
private static CompactNumberFormat generateCustomizedGermanCompactNumberFormat()
{
   final String[] germanyGermanCompactPatterns
      = {"", "", "", "0k", "00k", "000k", "0m", "00m", "000m", "0b", "00b", "000b", "0t", "00t", "000t"};
   final DecimalFormat germanyGermanDecimalFormat
      = acquireDecimalFormat(Locale.GERMANY);
   final CompactNumberFormat customGermanCompactNumberFormat
      = new CompactNumberFormat(
         germanyGermanDecimalFormat.toPattern(),
         germanyGermanDecimalFormat.getDecimalFormatSymbols(),
         germanyGermanCompactPatterns);
   return customGermanCompactNumberFormat;
}

There are three items worth special emphasis in the above code listing:

  1. The CompactNumberFormat(String, DecimalFormatSymbols, String[]) constructor allows for an array of Strings to be passed to the instance to specify the compact number patterns.
  2. The nature of the String[] defining compact number patterns is defined in the Javadoc for CompactNumberFormat class, which states, "The compact number patterns are represented in a series of patterns where each pattern is used to format a range of numbers."
    • That same Javadoc provides a US locale based example that provides these values for the range 100 to 1014 and it was that example that I adapted here.
    • More or fewer than 15 patterns can be supplied, but the first supplied pattern always corresponds to 100. The Javadoc states, "There can be any number of patterns and they are strictly index based starting from the range 100."
    • For demonstration purposes here, I adapted the patterns from an observation on the subreddit thread referenced earlier. I don't know much about German, but the argument for SI-based suffixes made a lot of sense and this is merely for illustrative purposes anyway.
  3. In this example, I retrieved the other two arguments to the CompactNumberFormat constructor from a JDK-provided instance of DecimalFormat for Germany German locale (Locale.GERMANY). This ensured that the decimal pattern and decimal format symbols of my custom Germany German instance of CompactNumberFormat would be the same as those associated with the JDK-provided instance.

The code listing above showed a call to a method called acquireDecimalFormat(Locale) to get a JDK-provided instance of DecimalFormat for Locale.GERMANY. For completeness, that method is shown next.

/**
 * Provides an instance of {@code DecimalFormat} associated with
 * the provided instance of {@code Locale}.
 *
 * @param locale Locale for which an instance of {@code DecimalFormat}
 *    is desired.
 * @return Instance of {@code DecimalFormat} corresponding to the
 *    provided {@code Locale}.
 * @throws ClassCastException Thrown if I'm unable to acquire a
 *    {@code DecimalFormat} instance from the static factory method
 *    on class {@code NumberFormat} (the approach recommended in the
 *    class-level Javadoc for {@code DecimalFormat}).
 */
private static DecimalFormat acquireDecimalFormat(final Locale locale)
{
   final NumberFormat generalGermanyGermanFormat
      = NumberFormat.getInstance(locale);
   if (generalGermanyGermanFormat instanceof DecimalFormat)
   {
      return (DecimalFormat) generalGermanyGermanFormat;
   }
   throw new ClassCastException(
        "Unable to acquire DecimalFormat in recommended manner;"
      + " presented with NumberFormat type of '"
      + generalGermanyGermanFormat.getClass().getSimpleName()
      + "' instead.");
}

The code snippets shown above demonstrate how the compact number patterns can be customized for a given instance of CompactNumberFormat when the compact number patterns associated in instances of that class for a given Locale are not desirable. It'd be nice if there was a method on the CompactNumberFormat class to allow for overriding of some or all of the compact number patterns associated with an existing instance obtained via NumberFormat static factory class, but JDK 12 has entered rampdown phase 2.

JDK 13: What AggressiveOpts?

The Java VM flag -XX:+AggressiveOpts was deprecated in JDK 11 [see JDK-8199777 and JDK-8199778] "because its behavior is ill-defined." The "Problem" section of JDK-8199778 further explains (I added the emphasis):

AggressiveOpts has been used as a catch-all method of enabling various experimental performance features, mostly targeted to improve score on very specific benchmarks. Most things it affected has been removed or integrated over time, leaving the behavior of the flag ill-defined and prone to cause more issues than it'll solve. The only effect that the flag currently has is setting AutoBoxCacheMax = 20000 and BiasedLockingStartupDelay = 500. Both can be done manually by setting the corresponding flags on the command line.

According to the document "Java HotSpot VM Options," the -XX:+AggressiveOpts flag was added with J2SE 5 Update 6 to "turn on point performance compiler optimizations that are expected to be default in upcoming releases."

The article "Java's -XX:+AggressiveOpts: Can it slow you down?" examines the -XX:+AggressiveOpts VM flag in detail and looks at some benchmark comparisons. The article concludes, "By retaining legacy flags you make it less likely to get the benefits of newer, faster features in released JVMs."

A much older Kirk Pepperdine article "Poorly chosen Java HotSpot Garbage Collection Flags and how to fix them!" specifically calls out -XX:+AggressiveOpts as an example of a VM flag whose behavior is unknown. Pepperdine writes that recommendations for use of this flag have not changed since Java SE 5.

When the -XX:+AggressiveOpts flag is passed to the JDK 11 Java launcher, a warning is presented: "VM warning: Option AggressiveOpts was deprecated in version 11.0 and will likely be removed in a future release."

In JDK 12, -XX:+AggressiveOpts has been removed as advertised (JDK-8150552) and a warning was presented to anyone trying to use it in conjunction with the Java launcher. The next screen snapshot displays this warning message that states, "VM warning: Ignoring option AggressiveOpts; support was removed in 12.0" (from JDK 12 Early Access Build #29 [2019/1/24]).

In JDK 13 Early Access builds, the VM won't start if -XX:+AggressiveOpts is specified. This is shown in the next screen snapshot (JDK 13 Early Access Build #5 [2019/1/24]).

As the previous image shows, the VM fails to start in JDK 13 when the -XX:+AggressiveOpts flag is specified and it reports the error message, "Unrecognized VM option 'AggressiveOpts'."

The -XX:+AggressiveOpts flag was deprecated in JDK 11, is removed but only shows a warning when specified in JDK 12, and is removed and prevents the VM from starting when specified in JDK 13.

Thursday, January 17, 2019

Using Minimum Fractional Digits with JDK 12 Compact Number Formatting

The post "Compact Number Formatting Comes to JDK 12" demonstrated the support added to NumberFormat in JDK 12 to support compact number formatting. The examples shown in that post only used the instances of NumberFormat returned by invocations of NumberFormat's new overloaded getCompactNumberInstance(-) methods and so therefore did not specify characteristics such as minimum fractional digits and maximum fractional digits. The results, in some cases, are less than desirable. Fortunately, NumberFormat does allow for minimum and maximum fractional digits to be specified and this post demonstrates how that can improve the output of the compact number formatting available with JDK 12.

The Javadoc documentation for new class java.text.CompactNumberFormat (currently available at https://download.java.net/java/early_access/jdk12/docs/api/java.base/java/text/CompactNumberFormat.html, but this location will change when JDK 12 is officially released in March) includes a section "Formatting" that states: "The default formatting behavior returns a formatted string with no fractional digits, however users can use the setMinimumFractionDigits(int) method to include the fractional part."

The code listing introduced in the original "Compact Number Formatting Comes to JDK 12" post (and which is available on GitHub) has been updated to demonstrate use of NumberFormat.setMinimumFractionDigits(int). An excerpt of that code is shown next and is followed by the accompanying output.

/**
 * Generates standardized map of labels to Compact Number Format
 * instances described by the labels. The instances of {@code NumberFormat}
 * are created with Locale and Style only and with the provided number
 * of minimum fractional digits.
 *
 * @return Mapping of label to an instance of a Compact Number Format
 *    consisting of a Locale, Style, and specified minimum number of fractional
 *    digits that is described by the label.
 */
private static Map<String, NumberFormat> generateCompactNumberFormats(
   final int minimumNumberFractionDigits)
{
   var numberFormats = generateCompactNumberFormats();
   numberFormats.forEach((label, numberFormat) ->
      numberFormat.setMinimumFractionDigits(minimumNumberFractionDigits));
   return numberFormats;
}


/**
 * Demonstrates compact number formatting in a variety of locales
 * and number formats against the provided {@code long} value and
 * with a minimum fractional digits of 1 specified.
 * @param numberToFormat Value of type {@code long} that is to be
 *    formatted using compact number formatting and a variety of
 *    locales and number formats and with a single minimal fractional
 *    digit.
 */
private static void demonstrateCompactNumberFormattingOneFractionalDigitMinimum(
   final long numberToFormat)
{
   final Map<String, NumberFormat> numberFormats = generateCompactNumberFormats(1);
   out.println(
      "Demonstrating Compact Number Formatting on long '" + numberToFormat
         + "' with 1 minimum fraction digit:");
   numberFormats.forEach((label, numberFormat) ->
      out.println("\t" +  label + ": " + numberFormat.format(numberToFormat))
   );
}
Demonstrating Compact Number Formatting on long '15' with 1 minimum fraction digit:
 Default: 15
 US/Long: 15
 UK/Short: 15
 UK/Long: 15
 FR/Short: 15
 FR/Long: 15
 DE/Short: 15
 DE/Long: 15
 IT/Short: 15
 IT/Long: 15
Demonstrating Compact Number Formatting on long '150' with 1 minimum fraction digit:
 Default: 150
 US/Long: 150
 UK/Short: 150
 UK/Long: 150
 FR/Short: 150
 FR/Long: 150
 DE/Short: 150
 DE/Long: 150
 IT/Short: 150
 IT/Long: 150
Demonstrating Compact Number Formatting on long '1500' with 1 minimum fraction digit:
 Default: 1.5K
 US/Long: 1.5 thousand
 UK/Short: 1.5K
 UK/Long: 1.5 thousand
 FR/Short: 1,5 k
 FR/Long: 1,5 millier
 DE/Short: 1.500
 DE/Long: 1,5 Tausend
 IT/Short: 1.500
 IT/Long: 1,5 mille
Demonstrating Compact Number Formatting on long '15000' with 1 minimum fraction digit:
 Default: 15.0K
 US/Long: 15.0 thousand
 UK/Short: 15.0K
 UK/Long: 15.0 thousand
 FR/Short: 15,0 k
 FR/Long: 15,0 mille
 DE/Short: 15.000
 DE/Long: 15,0 Tausend
 IT/Short: 15.000
 IT/Long: 15,0 mila
Demonstrating Compact Number Formatting on long '150000' with 1 minimum fraction digit:
 Default: 150.0K
 US/Long: 150.0 thousand
 UK/Short: 150.0K
 UK/Long: 150.0 thousand
 FR/Short: 150,0 k
 FR/Long: 150,0 mille
 DE/Short: 150.000
 DE/Long: 150,0 Tausend
 IT/Short: 150.000
 IT/Long: 150,0 mila
Demonstrating Compact Number Formatting on long '1500000' with 1 minimum fraction digit:
 Default: 1.5M
 US/Long: 1.5 million
 UK/Short: 1.5M
 UK/Long: 1.5 million
 FR/Short: 1,5 M
 FR/Long: 1,5 million
 DE/Short: 1,5 Mio.
 DE/Long: 1,5 Million
 IT/Short: 1,5 Mln
 IT/Long: 1,5 milione
Demonstrating Compact Number Formatting on long '15000000' with 1 minimum fraction digit:
 Default: 15.0M
 US/Long: 15.0 million
 UK/Short: 15.0M
 UK/Long: 15.0 million
 FR/Short: 15,0 M
 FR/Long: 15,0 million
 DE/Short: 15,0 Mio.
 DE/Long: 15,0 Millionen
 IT/Short: 15,0 Mln
 IT/Long: 15,0 milioni

As the example and output shown above demonstrate, use of NumberFormat.setMinimumFractionDigits(int) leads to compact number formatted output that is likely to be more aesthetically pleasing in many cases. There is a recent discussion "Compact Number Formatting and Fraction Digits" on the OpenJDK core-libs-dev mailing list that also discusses this ability to customize the compact number formatting output.

Wednesday, January 2, 2019

Restarting Java's Raw String Literals Discussion

It was announced in December 2018 that raw string literals would be dropped from JDK 12. Now, in the new year, discussion related to the design of raw string literals in Java has begun again.

In the post "Raw string literals -- restarting the discussion" on the amber-spec-experts OpenJDK mailing list, Brian Goetz references the explanation for dropping raw string literals preview feature from JDK 12 and suggests "restart[ing] the design discussion." Goetz summarizes the previous design discussions and decisions and lessons learned from the first take on raw string literals, discusses some design questions and trade-offs to be made, and then calls for input on three specific types of observation data:

  • "Data that supports or refutes the claim that our primary use cases are embedded JSON, HTML, XML, and SQL."
  • "Use cases we've left out..."
  • "Data (either Java or non-Java) on the use of various flavors of strings (raw, multi-line, etc) in real codebases..."

Jim Laskey posted two messages with the title "Enhancing Java String Literals Round 2" to the same amber-spec-experts mailing list and references an HTML version and a PDF version of an "RTL2" document that aids in the discussion of "Take Two" of raw string literals. Laskey outlines a "series of critical decision points that should be given thought, if not answers, before we propose a new design."

A few of the major decisions to be made as raw string literals for Java are reconsidered include these discussed in the aforementioned posts are listed here, but many more are contained in the posts:

  • Which is really more important to developers: "raw text" or "multi-line strings"?
  • Which character makes for the best delimiter for most Java developers and Java use cases?
  • How should incidental spacing be handled?

There has already been some feedback on the amber-dev OpenJDK mailing list. Stephen Colebourne provides "Extended string literals feedback" and Bruno Borges recommends "special assignment rather [than] special delimiters."

I often see developers complaining about certain language and API decisions after the decisions have been implemented. For anyone with strong feelings about the subject of raw string literals and multi-line strings in Java, now is an opportunity to make one's voice heard and to possibly influence the final design that will come to Java at some point in the future. Discussion has also started on the Java subreddit in two threads: "Raw string literals -- restarting the discussion" and "New RSL Proposal".

Wednesday, December 26, 2018

Compact Number Formatting Comes to JDK 12

JDK 12 Early Access Build 24 introduces support for Compact Number Formatting. The JDK-8188147 (Compact Number Formatting support) CSR's "Summary" is the simple sentence, "Adding support for the compact/short number formatting in JDK." That same CSR also provides a detailed "Solution" section that provides background on providing numbers in multiple compact forms for each Locale and the constructs/APIs added to the JDK to support this functionality (new class, new enum, new methods, etc.)

The representations of compact and short formats of numbers in each locale are based on the Unicode Common Locale Data Repository (CLDR). A newly added class, java.text.CompactNumberFormat, has class-level Javadoc comments that provide quite a bit of detail regarding how numbers are expressed in "short" and "long" compact number formats. That class's Javadoc comments also specify compact number patterns, formatting, parsing, and rounding (RoundingMode.HALF_EVEN by default) related to custom number formats.

In the request for review of the addition of compact number formatting to JDK 12, Nishit Jain writes:

The existing NumberFormat API provides locale based support for formatting and parsing numbers which includes formatting decimal, percent, currency etc, but the support for formatting a number into a human readable or compact form is missing. This RFE adds that feature to format a decimal number in a compact format (e.g. 1000 -> 1K, 1000000 -> 1M in en_US locale), which is useful for the environment where display space is limited, so that the formatted string can be displayed in that limited space. It is defined by LDML's specification for Compact Number Formats.

http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats

It is probably easiest to understand compact number formatting via code example. The following class (CompactNumberFormatDemo) was compiled and executed against JDK 12 Early Access Build 24 and is also available on GitHub.

package dustin.examples.jdk12.format;

import static java.lang.System.out;

import java.text.NumberFormat;
import java.util.Locale;

/**
 * Demonstrate Compact Number Format support added to
 * JDK 12 as of Early Access Build 24 (see also
 * JDK-8177552: Compact Number Formatting support).
 */
public class CompactNumberFormatDemo
{
   private static void demonstrateCompactNumberFormatting(final long numberToFormat)
   {
      final NumberFormat numberFormatDefault
         = NumberFormat.getCompactNumberInstance();
      final NumberFormat numberFormatUsLong
         = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
      final NumberFormat numberFormatUkShort
         = NumberFormat.getCompactNumberInstance(Locale.UK, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatUkLong
         = NumberFormat.getCompactNumberInstance(Locale.UK, NumberFormat.Style.LONG);
      final NumberFormat numberFormatFrShort
         = NumberFormat.getCompactNumberInstance(Locale.FRANCE, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatFrLong
         = NumberFormat.getCompactNumberInstance(Locale.FRANCE, NumberFormat.Style.LONG);
      final NumberFormat numberFormatGrShort
         = NumberFormat.getCompactNumberInstance(Locale.GERMANY, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatGrLong
         = NumberFormat.getCompactNumberInstance(Locale.GERMANY, NumberFormat.Style.LONG);
      final NumberFormat numberFormatItShort
         = NumberFormat.getCompactNumberInstance(Locale.ITALY, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatItLong
         = NumberFormat.getCompactNumberInstance(Locale.ITALY, NumberFormat.Style.LONG);

      out.println("Demonstrating Compact Number Formatting on '" + numberToFormat + "':");
      out.println("\tDefault:  " + numberFormatDefault.format(numberToFormat));
      out.println("\tUS/Long:  " + numberFormatUsLong.format(numberToFormat));
      out.println("\tUK/Short: " + numberFormatUkShort.format(numberToFormat));
      out.println("\tUK/Long:  " + numberFormatUkLong.format(numberToFormat));
      out.println("\tFR/Short: " + numberFormatFrShort.format(numberToFormat));
      out.println("\tFR/Long:  " + numberFormatFrLong.format(numberToFormat));
      out.println("\tDE/Short: " + numberFormatGrShort.format(numberToFormat));
      out.println("\tDE/Long:  " + numberFormatGrLong.format(numberToFormat));
      out.println("\tIT/Short: " + numberFormatItShort.format(numberToFormat));
      out.println("\tIT/Long:  " + numberFormatItLong.format(numberToFormat));
   }

   /**
    * Main demonstration executable.
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(final String[] arguments)
   {
      demonstrateCompactNumberFormatting(15);
      demonstrateCompactNumberFormatting(150);
      demonstrateCompactNumberFormatting(1500);
      demonstrateCompactNumberFormatting(15000);
      demonstrateCompactNumberFormatting(150000);
      demonstrateCompactNumberFormatting(1500000);
      demonstrateCompactNumberFormatting(15000000);
   }
}

When executed, the above code writes the following to standard output:

Demonstrating Compact Number Formatting on '15':
 Default:  15
 US/Long:  15
 UK/Short: 15
 UK/Long:  15
 FR/Short: 15
 FR/Long:  15
 DE/Short: 15
 DE/Long:  15
 IT/Short: 15
 IT/Long:  15
Demonstrating Compact Number Formatting on '150':
 Default:  150
 US/Long:  150
 UK/Short: 150
 UK/Long:  150
 FR/Short: 150
 FR/Long:  150
 DE/Short: 150
 DE/Long:  150
 IT/Short: 150
 IT/Long:  150
Demonstrating Compact Number Formatting on '1500':
 Default:  2K
 US/Long:  2 thousand
 UK/Short: 2K
 UK/Long:  2 thousand
 FR/Short: 2 k
 FR/Long:  2 millier
 DE/Short: 1.500
 DE/Long:  2 Tausend
 IT/Short: 1.500
 IT/Long:  2 mille
Demonstrating Compact Number Formatting on '15000':
 Default:  15K
 US/Long:  15 thousand
 UK/Short: 15K
 UK/Long:  15 thousand
 FR/Short: 15 k
 FR/Long:  15 mille
 DE/Short: 15.000
 DE/Long:  15 Tausend
 IT/Short: 15.000
 IT/Long:  15 mila
Demonstrating Compact Number Formatting on '150000':
 Default:  150K
 US/Long:  150 thousand
 UK/Short: 150K
 UK/Long:  150 thousand
 FR/Short: 150 k
 FR/Long:  150 mille
 DE/Short: 150.000
 DE/Long:  150 Tausend
 IT/Short: 150.000
 IT/Long:  150 mila
Demonstrating Compact Number Formatting on '1500000':
 Default:  2M
 US/Long:  2 million
 UK/Short: 2M
 UK/Long:  2 million
 FR/Short: 2 M
 FR/Long:  2 million
 DE/Short: 2 Mio.
 DE/Long:  2 Million
 IT/Short: 2 Mln
 IT/Long:  2 milione
Demonstrating Compact Number Formatting on '15000000':
 Default:  15M
 US/Long:  15 million
 UK/Short: 15M
 UK/Long:  15 million
 FR/Short: 15 M
 FR/Long:  15 million
 DE/Short: 15 Mio.
 DE/Long:  15 Millionen
 IT/Short: 15 Mln
 IT/Long:  15 milioni

The compact number format support that has been added to JDK 12 via Early Access Build 24 allows for formatting and parsing numeric representations in a locale-specific "long" or "short" compact forms.

Saturday, December 22, 2018

The Brief but Complicated History of JDK 12's String::transform Method

It was recently proposed that the Java preview feature Raw String Literals (JEP 326) be removed from JDK 12 and it is now official that the preview feature will be removed (version 25 of Java SE 12 [JSR 386] removes it). Several methods had been added to the JDK String class to support this feature. Those methods that were added to versions of the JDK prior to JDK 12 [such as String::lines] are likely to remain available even after the raw string literals preview feature has been removed. However, it has already been decided that one method added to String in JDK 12 (String::align) should be removed from JDK 12 as part of removing raw string literals. The method String::transform was added to JDK 12 and the remainder of this post looks at String::transform, as currently implemented in JDK 12, in more detail and discusses why its already controversial short history implies that it could be a potential candidate for removal along with raw string literals.

The current String::transform implementation has been available in JDK 12 Early Access Builds since Build 22 (Build 24 [15 December 2018] is latest available build as of this writing) and was introduced via JDK-8203442 ("String::transform").

There was quite a bit of discussion related to this method being added to the JDK. The following bullets outline key discussion points.

  • Jim Laskey wrote that the "originating goal" of String::transform was to "allow custom alignment methods for those developers not satisfied with String::align()"
  • Other messages further describe the motivation for, intent of, and benefits of String::transform:
    • RĂ©mi Forax wrote, "...it's nice to be able to be able to write code fluently from left to right..."
    • Jim Laskey wrote, "String::transform was intended to facilitate custom manipulation (alignment) of raw string literals, in the most string generalized way."
    • The "Description" of JDK-8203442 states, "The String::transform instance method allows the application of a lambda function to a string."
    • JDK-8203703 supplies examples to illustrate that "...steps can be discerned more clearly" with String::transform than with static methods in which the "reader is forced to interpret portions of the expression from the inside out."
  • String::transform originally returned String, but then was changed to return Object and Jim Laskey wrote about that change, "'transform' became generic when the case was made that other types might also be relevant." He concluded, "I might be led back to just supporting String."
  • The naming of String::transform has been challenging with some of the following names proposed (listed in alphabetic order):
  • RĂ©mi Forax has written that "more variants (transformToInt, transformToLong, transformToDouble) [are needed] to be useful."
  • Brian Goetz has described why the current plan is to implement this functionality via the method String::transform rather than an operator such as |>.
  • Stuart Marks has written that "this particular decision [String::transform] sets a precedent for the use of the name 'transform' for methods that do similar things on other classes" and references JDK-8140283 and JDK-8214753:
    • JDK-8140283 proposes the addition of the "chain" method for Stream and Optional to "mitigate" the "disrupt[ion of] the linear flow of the pipeline stages" when using methods that acts upon a Stream or Optional and return something that is itself "chainable").
    • JDK-8214753 proposes the addition of "Optional::transform" that would allow for "an arbitrary operation on an Optional."
  • There was some confusion and consternation related to how String::transform was added to OpenJDK 12, but Stuart Marks's message summarizes the events leading to the addition of this method.
    • A particularly interesting sentence in Marks's message states (I have added the emphasis): "While this API point stands on its own, this is really part of Jim's RSL work which includes several API additions to String, and which will likely have a significant effect on how String literals are used in Java code."
  • Tomasz Linkowski has pointed out that it's likely that String::transform (and any similar method added to Stream) will get used in select cases where there are easier ways to do the same thing already without the new method. The examples he provides of potential misuse of String::transform are "string.transform(String::toLowerCase)" and "stream.chain(s->s.map(mapper))".

Two online examples demonstrate how String::transform might be used in its most common use cases:

  • JDK-8203703 ("String::transform") provides a "Solution" example that demonstrates how String::transform can improve code readability by allowing for operations acting on Strings to be read in order from left-to-right rather than being read "from the inside out."
  • A message on the core-libs-dev mailing list provides an example of using String::transform to convert a String into an instance of a class other than String.

Stephen Colebourne asked the same question I was wondering when I read that raw string literals were to be removed from JDK 12: "Is String::transform going to be removed as well given the removal of raw strings and its controversial nature?" Although I have not seen anything authoritative and definitive regarding whether String::transform will remain in JDK 12, there are three pieces of evidence that lead me to think it will be staying.

  1. I have not seen anything saying that String::transform, which is already in JDK 12 as of Early Access Build 22, is to be removed. There are issues written to remove compiler support associated with raw string literals and even to remove another String method (String::align), but I'm not aware of a similar issue written for String::transform.
  2. It has been stated that while String::transform was added as part of the raw string literal work, it was also stated that String::transform "stands on its own."
  3. The two examples I cited earlier on how to use this method do not rely on or require raw string literals. In other words, the method can be used regardless of the presence or absence of raw string literals.

String::transform has not been around for a long time (less than one year), but it already has significant history. The method is available currently in JDK 12 (since Early Access Build 22) and I suspect it will remain part of String's API despite the removal of raw string literals from JDK 12.

Tuesday, December 11, 2018

Dropping Raw String Literals from JDK 12

It has been proposed that raw string literals (preview) be dropped from JDK 12 (which enters Rampdown Phase One on December 13). Brian Goetz has written a detailed description of the motivations for dropping this preview feature (JEP 326). There is also discussion on this on the Java subreddit. In the post "JSR 386 (Java SE 12) JEP Propose to Drop: 326: Raw String Literals (Preview)," Iris Clark writes that JEP 326 "of scope 'SE' has been Proposed to Drop for Java SE 12."

In Goetz's explanation for the proposal to remove raw string literals preview functionality from JDK 12, he writes, "The Preview Feature mechanism is intended for features for which there is a high confidence that the feature is 'done', and the likelihood that significant changes would be made before making the feature permanent is low." Goetz adds, "I am no longer convinced that we've yet got to the right set of tradeoffs between complexity and expressiveness, or that we've explored enough of the design space to be confident that the current design is the best we can do. By withdrawing, we can continue to refine the design, explore more options, and aim for a preview that actually meets the requirements of the Preview Feature process (JEP 12)."

Goetz also provides a sample of the feedback items they've received regarding raw string literals preview design and implementation. He concludes the message with the statement, "Discussion on the technical details of this feature can continue to take place on the amber-* lists" (amber-dev, amber-spec-comments, amber-spec-experts, and amber-spec-observers).

It sounds like there are still plans for raw string literals to come to Java, but they'll be implemented differently than they are currently in JDK 12 Early Access Builds.

Friday, November 30, 2018

4 New JEPs Proposed for Java 12

Four JEPs have just been proposed for JDK 12 and they are briefly summarized in this post based on the JEPs' own text descriptions.

JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)

JEP 189 ["Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)"] is described as "a new garbage collection (GC) algorithm ... [that] reduces GC pause times by doing evacuation work concurrently with the running Java threads." The JEP text adds, "Shenandoah is an appropriate algorithm for applications which value responsiveness and predictable short pauses."

JEP 334: JVM Constants API

JEP 334 ["JVM Constants API"]'s "Summary" states that this JEP's goal is to "Introduce an API to model nominal descriptions of key class-file and run-time artifacts, in particular constants that are loadable from the constant pool." This JEP is also proposed for Java SE 12.

JEP 344: Abortable Mixed Collections for G1

JEP 344 ["Abortable Mixed Collections for G1"] aims to "make G1 mixed collections abortable if they might exceed the pause target."

JEP 346: Promptly Return Unused Committed Memory from G1

JEP 346 ["Promptly Return Unused Committed Memory from G1"] is another JEP related to the G1 garbage collector and it aims to "enhance the G1 garbage collector to automatically return Java heap memory to the operating system when idle."

Friday, November 16, 2018

JDK 12's Files.mismatch Method

JDK 12 introduces a new method to the Files class. The method, Files.mismatch(Path,Path), has been introduced to JDK 12 via JDK-8202302 and is available in JDK 12 Early Access Build 20 (same early access build that supports the new {@systemProperty} Javadoc tag).

JDK-8202302 ["(fs) New Files.mismatch method for comparing files"] adds the Files.mismatch(Path,Path) method "to compare the contents of two files to determine whether there is a mismatch between them" and can be used to determine "whether two files are equal." There was talk at one time of adding a Files.isSameContent() method, but it was decided to use Files.mismatch(Path,Parh) because of its consistency "with the Arrays.mismatch and Buffer.mismatch methods."

The next code listing contains a simple Java class that demonstrates the new Files.mismatch(Path,Path) and contrasts it with Files.isSameFile(Path,Path). The full source code is available on GitHub.

package dustin.examples.jdk12.files;

import java.nio.file.Files;
import java.nio.file.Path;

import static java.lang.System.out;

/**
 * Demonstrate {@code Files.mismatch(Path,Path)} introduced with JDK 12
 * and useful for determining if two files have the same content even
 * if they're not the same files.
 */
public class FilesDemo
{
   public static void main(final String[] arguments) throws Exception
   {
      if (arguments.length < 2)
      {
         out.println("USAGE: FilesDemo <file1Name> <file2Name>");
         return;
      }

      final String file1Name = arguments[0];
      final Path file1Path = Path.of(file1Name);
      final String file2Name = arguments[1];
      final Path file2Path = Path.of(file2Name);

      out.println("\nFiles '" + file1Name + "' and '" + file2Name + "' are "
         + (Files.isSameFile(file1Path, file2Path) ? "the" : "NOT the")
         + " same.\n\n");
      out.println("\nFiles '" + file1Name + "' and '" + file2Name + "' are "
         + (Files.mismatch(file1Path, file2Path) == -1 ? "the" : "NOT the")
         + " same content.\n\n");
   }
}

When the above code is executed against various combinations of files, it provides results that are captured in the next table. Note that the Files.mismatch method does not itself return a boolean, but the code shown above adapts its response to the appropriate boolean.

Results of Code Snippet Using Files.isSameFile() and Files.mismatch()
Files Relationship Files.isSameFile(Path,Path) Files.mismatch(Path,Path)
Same File "Same" (true) "Same" (-1)
Copied File "Not Same" (false) "Same" (-1)
Different Files "Not Same" (false) "Not Same" (positive integer)
Soft-linked "Same" (true) "Same" (-1)
Hard-linked "Same" (true) "Same" (-1)
Either File Not Found FileNotFoundException FileNotFoundException

The addition of Files.mismatch(Path,Path) is another step in accomplishing JDK-6852033 ["Inputs/Outputs methods to make common I/O tasks easy to do"] and makes it easier to determine when two files that are not the same file are still "equal" or have the same content.

Thursday, November 15, 2018

JDK 12 Javadoc Tag for System Properties

JDK 12 Early Access Build 20 (2018/11/15) is available and can be used to try out the new Javadoc tag {@systemProperty}. The new {@systemProperty} Javadoc tag is discussed in the core-libs-dev mailing list message "FYI: new javadoc tag to document system properties" and was introduced in response to JDK-5076751 ["System properties documentation needed in javadocs"].

The {@systemPropery} Javadoc tag displays its contents as normal text in its generated output and makes the contents available to the Javadoc search introduced with JDK 9. This tag is intended to be used for documenting an application's system properties.

The following simple class will be used to demonstrate the new JDK 12 Javadoc tag {@systemProperty}:

package dustin.examples.jdk12.properties;

import static java.lang.System.out;

/**
 * Class with sole purpose to illustrate JDK 12's
 * support for {@literal {@systemProperty}}.
 */
public class PropertiesDemo
{
   /**
    * {@systemProperty blog.title} can be specified to
    * provide a blog title.
    */
   private final static String PROPERTY_NAME = "blog.title";

   public static void main(final String[] arguments)
   {
      final String property = System.getProperty(PROPERTY_NAME);
      out.println("Property " + PROPERTY_NAME + ": " + property);
   }
}

The above code example applies {@systemProperty} to the private attribute PROPERTY_NAME. Because the field if private, the Javadoc tool must be executed with the -private flag to have documentation generated for this field.

The next screen snapshot demonstrates the documentation generated for the simple class using the javadoc command-line tool included in JDK 12 Early Access Build 12 (which did not have support for {@systemProperty}).

The red ovals in the previous screen snapshot show that the {@systemProperty} tag is not handled properly in earlier versions of the JDK. The contents of that tag are NOT displayed and the "search" functionality does not match on the system property name.

The next screen snapshot demonstrates the documentation generated for this same class using the command-line javadoc that comes with JDK 12 Early Access Build 20.

The green ovals in the previous screen snapshot show that {@systemProperty} is better supported in Early Access Build 20 of OpenJDK JDK 12. The contents of that tag are correctly displayed in the Javadoc itself and the search capability now matches on the system property name. It is also worth noting that the text within {@systemProperty} is presented in the HTML with appearance similar to which {@code} is presented.

The addition of {@systemProperty} potentially makes it easier for developers to find relevant descriptions of system properties for an application among Javadoc-generated documentation. The aforementioned post "FYI: new javadoc tag to document system properties" discusses other Javadoc enhancements that could possibly be made to take advantage of this tag. The potential enhancements include "a 'summary page' that lists all the system properties", adding "information regarding the 'scope' of the definition", and allowing "a short description to be included in the {@systemProperty} tag" that "could be included in the search index, the A-Z index, and the summary page."

The Jonathan Gibbons FYI mailing list message that introduces {@systemProperty} also spells out its recommended usage:

Where should the tag be used? The tag should be used in the text of the defining instance of the property. This is where the characteristics of the system property are described, which may include information like: "what is the property for", "how and when is it set", "can it be modified", and so on.

The addition of {@systemProperty} to the Javadoc tool with JDK 12 Early Access Build 20 is a minor thing, but will allow developers to make documentation of important system properties more readily accessible for fellow developers.

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.

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

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.

Tuesday, September 11, 2018

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