Friday, March 26, 2021

Implementing equals(Object) with instanceof Pattern Matching

Pattern matching for the instanceof operator was introduced as a preview feature with JDK 14 and was finalized with JDK 16. Because instanceof pattern matching is finalized for JDK 16, it is not surprising to now see changes being made to the JDK to take advantage of pattern matching for the instanceof operator. These changes to the JDK to leverage instanceof pattern matching can provide ideas and examples for where to begin applying this in our own code. In this post, I look at the use of instanceof pattern matching in implementation of the ubiquitous equals(Object) methods.

In a message posted to the core-libs-dev OpenJDK mailing list related to a code review for JDK-8263358 ("Update java.lang to use instanceof pattern variable"), Brian Goetz provided a reminder that a standard approach used in implementation of equals(Object) can now be modified to take advantage of pattern matching for instanceof.

In the message, Goetz uses an example of how we have often implemented equals(Object) (but using instanceof pattern matching in this example consistent with the past):

if (!(o instanceof Key that)) return false;
return name == that.name && Arrays.equals(ptypes, that.ptypes);

Goetz points out that this can now be written with a single statement, in this manner:

return (o instanceof Key that)
   && name == that.name
   && Arrays.equals(ptypes, that.ptypes);

Goetz's message concludes with this:

The use of "if it's not, return false" is a holdover from when we couldn't express this as a single expression (which is almost always preferable), which means we had to fall back to control flow. Now we don't have to.

A new commit was made based on Goetz's feedback and that commit is Commit e9d913152cefda827e01c060a13f15eacc086b33. One can review the several changes associated with this commit to see multiple statements converted into single statements in the various equals(Object) methods.

Being able to use instanceof pattern matching to implement equals(Object) with one fewer statement is a small improvement that is nevertheless welcome.

Saturday, February 27, 2021

Java NullPointerException Avoidance and Enhancement Tactics

An encountered NullPointerException can be a useful mechanism for highlighting when a certain code flow or certain data has led to unexpected results (and the messages provided by NullPointerException are much improved with JDK 15). However, there are other times when the presence of null is not an exceptional condition and for those such cases there are several tactics that can be used to easily and cleanly avoid an unwanted NullPointerException. Even when the occurrence of a NullPointerException helps identify problems, there are other tactics we can use to make the most of these opportunities.

The code samples featured in this post are part of class NullSafeTactics and its full source code is available on GitHub.

Contents

Elegantly Avoiding Unnecessary NullPointerExceptions

Implicit Java String Conversion

There are often times when we want the string representation of something that is potentially null and we do not want the access of that string representation to result in a NullPointerException. An example of this is when we log certain conditions and the context we include in the logged message includes a variable or field that is null. It is highly unlikely in such a case that we want a NullPointerException to be possibly thrown during the attempted logging of some potentially different condition. Fortunately, Java's string conversion is often available in these situations.

Even when the field variable NULL_OBJECT of type Object is null, the following code will NOT result in a NullPointerException thanks to Java's string conversion handling a null implicitly and converting it to the "null" string instead.

/**
 * Demonstrates that Java string conversion avoids {@link NullPointerException}.
 */
public void demonstrateNullSafeStringConversion()
{
   executeOperation(
      "Implicit Java String Conversion",
      () -> "The value of the 'null' object is '" + NULL_OBJECT + "'.");
}

The output from running the above code snippet demonstrates that the NullPointerException does not get thrown.

Feb 25, 2021 9:26:19 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Implicit Java String Conversion' completed without exception!

The implicit Java string conversion avoided a NullPointerException. When toString() is explicitly called on that same null, a NullPointerException is encountered. This is shown in the next code listing and the output it leads to is shown after the code listing.

/**
 * Demonstrates that explicit {@link Object#toString()} on {@code null} leads to
 * {@link NullPointerException}.
 */
public void demonstrateNullUnsafeExplicitToString()
{
   executeOperation(
      "Unsafe Explicit toString() Invocation on null",
      () -> "The value of the 'null' object is '" + NULL_OBJECT.toString() + "'.");
}
Feb 25, 2021 9:32:06 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
SEVERE: Exception encountered while trying to run operation for demonstration 'Unsafe Explicit toString() Invocation on null': java.lang.NullPointerException: Cannot invoke "Object.toString()" because "dustin.examples.nullsafe.tactics.NullSafeTactics.NULL_OBJECT" is null

Note that these examples in this post have been executed with a JDK 17 early access release, so the NullPointerExceptions shown in this post benefit from the better NPE messages introduced with JDK 14 (and are enabled by default since JDK 15).

Null-safe String Representation with String.valueOf(Object)

Allowing Java's implicit string conversion to represent null as the "null" string is the cleanest and easiest way to handle null when constructing strings. However, there are many times when we need a string representation of a Java object when implicit string conversion is not available. In such cases, String.valueOf(Object) can be used to achieve functionality similar to the implicit string conversion. When an object is passed to String.valueOf(Object), that method will return the results of the object's toString() if that object is not null or will return the "null" string if the object is null.

The following code listing demonstrates String.valueOf(Object) in action and the output from running that code is shown after the code listing.

/**
 * Demonstrates that {@link String#valueOf(Object)} will render {@code null} safely
 * as "null" string.
 *
 * In many cases, use of {@link String#valueOf(Object)} is unnecessary because Java's
 * string conversion will perform the same effect. {@link String#valueOf(Object)} is
 * necessary when Java is not able to implicitly convert to a {@link String}.
 *
 * See also https://marxsoftware.blogspot.com/2009/04/value-of-stringvalueof.html.
 */
public void demonstrateNullSafeStringValueOf()
{
   executeOperation(
      "Null-safe String Representation with String.valueOf(Object)",
      () -> "The value of the 'null' object is '" + String.valueOf(NULL_OBJECT) + "'.");
}
Feb 25, 2021 10:05:52 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Null-safe String Representation with String.valueOf(Object)' completed without exception!

There are several overloaded versions of String#valueOf accepting parameter types other than Object, but they all behave similarly.

Null-safe String Representation with Objects.toString(Object)

The Objects class provides several methods to allow for elegant handling of potential nulls. One of these, Objects.toString(Object) works exactly like the just-discussed String.valueOf(Object). In fact, as described in the post "String.valueOf(Object) versus Objects.toString(Object)", the Objects.toString(Object) method delegates to the String.valueOf(Object) method.

The following code listing demonstrates use of Objects.toString(Object) and the output from running it follows the code listing.

/**
 * Demonstrates that {@link Objects#toString(Object)} will render {@code null} safely
 * as "null" string.
 *
 * In many cases, use of {@link Objects#toString(Object)} is unnecessary because Java's
 * string conversion will perform the same effect. {@link Objects#toString(Object)} is
 * necessary when Java is not able to implicitly convert to a {@link String}.
 */
public void demonstrateObjectsToString()
{
   executeOperation(
      "Null-safe String Representation with Objects.toString(Object)",
      () -> "The value of the 'null' object is '" + Objects.toString(NULL_OBJECT) + "'.");
}
Feb 25, 2021 10:19:52 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Null-safe String Representation with Objects.toString(Object)' completed without exception!

I tend to use String.valueOf(Object) instead of Objects.toString(Object) because the latter calls the former anyway and because there are overloaded versions of String#valueOf.

Null-safe String Representation with Objects.toString(Object, String)

The approaches covered so far in this post (implicit string conversion, String#valueOf methods, and Objects.toString(Object)) all result in the "null" string when a null is presented to them. There are times when we may prefer to have something other than the "null" string be presented as the string representation of null. An example of this is when we want to return an empty string from a method rather than returning null from a method. The following code listing demonstrates using Objects.toString(Object, String) to have an empty string be provided when first passed-in argument turns out to be null.

/**
 * Demonstrates that {@link Objects#toString(Object, String)} will render {@code null}
 * potentially safely as the "default" string specified as the second argument.
 *
 * In many cases, use of {@link Objects#toString(Object, String)} is unnecessary because
 * Java's string conversion will perform the same effect. {@link Objects#toString(Object)}
 * is necessary when Java is not able to implicitly convert to a {@link String} or when
 * it is desired that the string representation of the {@code null} be something other
 * than the "null" string.
 */
public void demonstrateObjectsToStringWithDefault()
{
   executeOperation(
      "Null-safe String Representation with Objects.toString(Object,String) Using Empty String Default",
      () -> "The value of the 'null' object is '" + Objects.toString(NULL_OBJECT, "") + "'.");
}
Feb 25, 2021 10:33:16 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Null-safe String Representation with Objects.toString(Object,String) Using Empty String Default' completed without exception!

Default Value Replacement of null for Any Object

The JDK-provided methods covered so far are useful for safely acquiring string representation of objects that might be null. Sometimes, we may want to handle a potential instance that might be null of a class other than String. In that case, the Objects.requireNonNullElse(T, T) method allows specification of a default value that should be used if the object in question (first parameter to the method) is null. This is demonstrated with the following code listing and its accompanying output that follows it.

/**
 * Demonstrates that {@link Objects#requireNonNullElse(Object, Object)} will render
 * {@code null} safely for any potential {@code null} passed to it by returning the
 * supplied default instead when the object in question is {@code null}. Two
 * examples are included in this method's demonstration:
 * 
    *
  1. {@code null} {@link Object} safely rendered as custom supplied default "null" string
  2. *
  3. {@code null} {@link TimeUnit} safely rendered as custom supplied default {@link TimeUnit#SECONDS}
  4. *
* * In many cases, use of {@link Objects#requireNonNullElse(Object, Object)} is not * necessary because Java's string conversion will perform the same effect. * {@link Objects#requireNonNullElse(Object, Object)} is necessary when Java is not * able to implicitly convert to a {@link String} or when the potentially {@code null} * object is not a {@link String} or when the object to have a default returned * when it is {@code null} is of class other than {@link String}. */ public void demonstrateNullSafeObjectsRequireNonNullElse() { executeOperation( "Null-safe String Representation with Objects.requireNonNullElse(Object, Object)", () -> "The value of the 'null' object is '" + Objects.requireNonNullElse(NULL_OBJECT, "null") + "'"); executeOperation("Null-safe TimeUnit access with Objects.requireNonNullElse(Object, Object)", () -> "The value used instead of 'null' TimeUnit is '" + Objects.requireNonNullElse(NULL_TIME_UNIT, TimeUnit.SECONDS) + "'"); }
Feb 28, 2021 2:54:45 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Null-safe String Representation with Objects.requireNonNullElse(Object, Object)' completed without exception!
Feb 28, 2021 2:54:45 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Null-safe TimeUnit access with Objects.requireNonNullElse(Object, Object)' completed without exception!

Another Objects method with slightly different name (requireNonNullElseGet(T, Supplier<? extends T>)) allows the default that will be used in place of null to be specified using a Supplier. The advantage of this approach is that the operation used to compute that default value will only be executed if the object is null and the cost of executing that Supplier is NOT incurred if the specified object is null (Supplier deferred execution).

Comparing enums Safely

Although Java enums can be compared for equality using Enum.equals(Object), I prefer to use the operators == and != for comparing enums because the latter is null-safe (and arguably makes for easier reading).

The code listing and associated output that follow demonstrate that comparing enums with == is null-safe but comparing enums with .equals(Object) is NOT null-safe.

/**
 * Demonstrates that comparing a potentially {@code null} enum is
 * {@code null}-safe when the {@code ==} operator (or {@code !=}
 * operator) is used, but that potentially comparing a {@code null}
 * enum using {@link Enum#equals(Object)} results in a
 * {@link NullPointerException}.
 *
 * See also https://marxsoftware.blogspot.com/2011/07/use-to-compare-java-enums.html.
 */
public void demonstrateEnumComparisons()
{
   executeOperation(
      "Using == with enums is null Safe",
      () -> NULL_TIME_UNIT == TimeUnit.MINUTES);
   executeOperation(
      "Using .equals On null Enum is NOT null Safe",
      () -> NULL_TIME_UNIT.equals(TimeUnit.MINUTES));
}
INFO: Demonstration 'Using == with enums is null Safe' completed without exception!
Feb 28, 2021 4:30:17 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
SEVERE: Exception encountered while trying to run operation for demonstration 'Using .equals On null Enum is NOT null Safe': java.lang.NullPointerException: Cannot invoke "java.util.concurrent.TimeUnit.equals(Object)" because "dustin.examples.nullsafe.tactics.NullSafeTactics.NULL_TIME_UNIT" is null
Feb 28, 2021 4:30:17 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation

Comparing Objects Safely with Known Non-null Object on LHS of .equals(Object)

When we know that at least one of two objects being compared is definitely NOT null, we can safely compare the two objects (even if the other one may be null), by calling Object.equals(Object) against the known non-null object. There still is an element of risk here if the class which you're calling .equals(Object) against has its Object.equals(Object) method implemented in such a way that passing in a null argument leads to a NullPointerException. However, I've never encountered a JDK class or even custom class that has made that mistake (and it is a mistake in my opinion to have a Object.equals(Object) overridden method not be able to handle a supplied null and simply return false in that case instead of throwing NullPointerException). The tactic of calling .equals(Object) against the known non-null object is demonstrated in the next code listing and associated output.

/**
 * Demonstrates that comparisons against known non-{@code null} strings can be
 * {@code null}-safe as long as the known non-{@code null} string is on the left
 * side of the {@link Object#equals(Object)} method ({@link Object#equals(Object)})
 * is called on the known non-{@code null} string rather than on the unknown
 * and potential {@code null}.
 */
public void demonstrateLiteralComparisons()
{
   executeOperation(
      "Using known non-null literal on left side of .equals",
         () -> "Inspired by Actual Events".equals(NULL_STRING));
   executeOperation(
      "Using potential null variable on left side of .equals can result in NullPointerExeption",
      () -> NULL_STRING.equals("Inspired by Actual Events"));
}
Feb 28, 2021 4:46:20 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Using known non-null literal on left side of .equals' completed without exception!
Feb 28, 2021 4:46:20 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
SEVERE: Exception encountered while trying to run operation for demonstration 'Using potential null variable on left side of .equals can result in NullPointerExeption': java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "dustin.examples.nullsafe.tactics.NullSafeTactics.NULL_STRING" is null

Although it was specifically String.equals(Object) demonstrated above, this tactic applies to instances of any class as long as the class's .equals(Object) method can gracefully handle a supplied null (and I cannot recall ever encountering one that didn't handle null).

Case Insensitive Comparison of Strings Safely with Known Non-null String on LHS of .equals(Object)

Placing the known non-null object on the left side of the .equals(Object) call is a general null-safe tactic for any object of any type. For String in particular, there are times when we want a null-safe way to compare two strings without regard to the case of the characters in the strings (case insensitive comparison). The String.equalsIgnoreCase(String) method works well for this and will be a null-safe operation if we use a known non-null String on the left side of that method (method called against the known non-null String).

The code listing and associated output that follow demonstrate null-safe use of String.equalsIgnoreCase(String).

/**
 * Demonstrates that case-insensitive comparisons against known non-{@code null}
 * strings can be {@code null}-safe as long as the known non-{@code null} string
 * is on the left side of the {@link Object#equals(Object)} method
 * ({@link Object#equals(Object)}) is called on the known non-{@code null} String
 * rather than on the unknown potential {@code null}).
 */
public void demonstrateLiteralStringEqualsIgnoreCase()
{
   executeOperation(
      "String.equalsIgnoreCase(String) is null-safe with literal string on left side of method",
      () -> "Inspired by Actual Events".equalsIgnoreCase(NULL_STRING));
   executeOperation(
      "Using potential null variable of left side of .equalsIgnoreCase can result in NPE",
      () -> NULL_STRING.equalsIgnoreCase("Inspired by Actual Events"));
}
Feb 28, 2021 7:03:42 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'String.equalsIgnoreCase(String) is null-safe with literal string on left side of method' completed without exception!
Feb 28, 2021 7:03:42 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
SEVERE: Exception encountered while trying to run operation for demonstration 'Using potential null variable of left side of .equalsIgnoreCase can result in NPE': java.lang.NullPointerException: Cannot invoke "String.equalsIgnoreCase(String)" because "dustin.examples.nullsafe.tactics.NullSafeTactics.NULL_STRING" is null

These last two demonstrations used literal strings as the "known non-null" strings against which methods were called, but other strings and objects could also be used. Constants and known previously initialized fields and variables are all candidates for the objects against which the comparison methods can be called safely as long as it's known that those fields and variables can never be changed to null. For fields, this condition is only guaranteed if that field is always initialized to a non-null value with the instance and is immutable. For variables, this condition is only guaranteed if that variable is initialized to an unknown value and is final. There are many "in between" cases where it is most likely that certain objects are not null, but guarantees cannot be made. In those cases, it is less risky to explicitly check each object being compared for null before comparing them with .equals(Object) or to use the Objects.equals(Object, Object) method, which is covered next.

Safely Comparing Objects When Neither is Known to be Non-null

The Objects.equals(Object, Object) method is a highly convenient way to compare two objects for equality when we don't know whether either or both might be null. This convenience method's documentation explains its behavior and it's probably what most of u would do if writing this code ourselves, "Returns true if the arguments are equal to each other and false otherwise. Consequently, if both arguments are null, true is returned. Otherwise, if the first argument is not null, equality is determined by calling the equals method of the first argument with the second argument of this method. Otherwise, false is returned."

This is demonstrated in the next code listing and associated output.

/**
 * Demonstrates that comparisons of even potential {@code null}s is safe
 * when {@link Objects#equals(Object, Object)} is used.
 */
public void demonstrateObjectsEquals()
{
   executeOperation(
      "Using Objects.equals(Object, Object) is null-safe",
      () -> Objects.equals(NULL_OBJECT, LocalDateTime.now()));
}
Feb 28, 2021 5:11:19 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Using Objects.equals(Object, Object) is null-safe' completed without exception!

I like to use the Objects.equals(Object, Object) to quickly build my own class's .equals(Objects) methods in a null-safe manner.

The method Objects.deepEquals(Object, Object) is not demonstrated here, but it's worth pointing out its existence. The method's documentation states, "Returns true if the arguments are deeply equal to each other and false otherwise. Two null values are deeply equal. If both arguments are arrays, the algorithm in Arrays.deepEquals is used to determine equality. Otherwise, equality is determined by using the equals method of the first argument."

Null-safe Hashing

The methods Objects.hashCode(Object) (single object) and Objects.hash(Object...) (sequences of objects) can be used to safely generate hash codes for potentially null references. This is demonstrated in the following code listing and associated output.

/**
 * Demonstrates that {@link Objects#hashCode(Object)} is {@code null}-safe.
 */
public void demonstrateObjectsHashCode()
{
   executeOperation(
      "Using Objects.hashCode(Object) is null-safe",
      () -> Objects.hashCode(NULL_OBJECT));
}

/**
 * Demonstrates that {@link Objects#hash(Object...)} is {@code null}-safe.
 */
public void demonstrateObjectsHash()
{
   executeOperation(
      "Using Objects.hash(Object...) is null-safe",
      () -> Objects.hash(NULL_OBJECT, NULL_STRING, NULL_TIME_UNIT));
}
Feb 28, 2021 5:11:19 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Using Objects.hashCode(Object) is null-safe' completed without exception!
Feb 28, 2021 5:11:19 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
INFO: Demonstration 'Using Objects.hash(Object...) is null-safe' completed without exception!

These methods can be convenient for generating one's own null-safe hashCode() methods on custom classes.

It's also important to note that there is a warning in the documentation that the hash code generated by Objects.hash(Object...) for a single supplied Object is not likely to be the same value as a hash code generated for that same Object when calling the Object's own hashCode() method or when calling Objects.hashCode(Object) on that Object.

Elegantly Handling Useful NullPointerExceptions

The tactics described and demonstrated so far were primarily aimed at avoiding NullPointerException in situations where we fully anticipated a reference being null but that presence of a null is in no way exceptional and so we don't want any exception (including NullPointerException) to be thrown. The remainder of the descriptions and examples in this post will focus instead of situations where we want to handle a truly unexpected (and therefore exceptional) null as elegantly as possible. In many of these cases, we do NOT want to preclude the NullPointerException from being thrown because its occurrence will communicate to us some unexpected condition (often bad data or faulty upstream code logic) that we need to address.

The improved NullPointerException messages have made unexpected NullPointerExceptions far more meaningful. However, we can often take a few additional tactics to further improve the usefulness of the NullPointerException that is thrown when we run into an unanticipated null. These tactics include adding our own custom context details to the exception and throwing the exception early so that a bunch of logic is not performed needlessly that may also need to be reverted.

Controlling When and What Related to Unexpected null

I like to use Objects.requireNonNull(T, String) at the beginning of my public methods that accept arguments which will lead to a NullPointerException if a passed-in argument is null. While a NullPointerException is thrown in either case (either implicitly when an attempt to deference the null or when Objects.requireNonNull(T, String) is called), I like the ability to be able to specify a string with details and context about what's happing when the null is unexpectedly encountered.

The Objects.requireNonNull(T) method does not allow one to specify a string with additional context, but it is still a useful guard method. Both of these methods allow the developer to take control of when a NullPointerException will be thrown for the unexpected null and this control allows the developer to preclude unnecessary logic from being performed. We'd rather not waste time/cycles on something that is going to lead to that exception anyway and we can often choose places in the code where it's cleaner to check for null and throw the exception to avoid having to "undo" pr "revert" logic that has been performed.

The following code listing and associated output demonstrate both of these methods in action.

/**
 * Demonstrates using {@link Objects#requireNonNull(Object)} and
 * {@link Objects#requireNonNull(Object, String)} to take control of
 * when an {@link NullPointerException} is thrown. The method accepting
 * a {@link String} also allows control of the context that is provided
 * in the exception message.
 *
 * It is not demonstrated here, but a similar method is
 * {@link Objects#requireNonNull(Object, Supplier)} that allows a
 * {@link Supplier} to be used to provide the message for when an
 * unexpected {@code null} is encountered.
 */
public void demonstrateObjectsRequiresNonNullMethods()
{
   executeOperation(
      "Using Objects.requireNonNull(T)",
      () -> Objects.requireNonNull(NULL_OBJECT));

   executeOperation(
      "Using Objects.requireNonNull(T, String)",
      () -> Objects.requireNonNull(NULL_OBJECT, "Cannot perform logic on supplied null object."));
}
Feb 28, 2021 5:59:42 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
SEVERE: Exception encountered while trying to run operation for demonstration 'Using Objects.requireNonNull(T)': java.lang.NullPointerException
Feb 28, 2021 5:59:42 PM dustin.examples.nullsafe.tactics.NullSafeTactics executeOperation
SEVERE: Exception encountered while trying to run operation for demonstration 'Using Objects.requireNonNull(T, String)': java.lang.NullPointerException: Cannot perform logic on supplied null object.

The output shows us that the method that accepted a String was able to provide that additional context in its message, which can be very useful when figuring out why the unexpected null occurred.

I don't demonstrate it here, but it's worth noting that another overloaded version of this method (Objects.requireNonNull(T, Supplier<String>)) allows a developer to use a Supplier to supply a custom NullPointerException for complete control over the exception that is thrown. The use of the Supplier and its deferred execution means that this exception generation will only be performed when the object is null. One might choose to implement this Supplier as a relatively expensive operation checking various data sources and/or instance values and would not need to worry about incurring that cost unless the unexpected null was encountered.

Other Null-Handling Tactics

There are other tactics that can be used to either avoid unnecessary NullPointerExceptions or to make NullPointerExceptions due to unexpected null more useful. These include explicit checking for null in conditionals and use of Optional.

Conclusion

This post has discussed and demonstrated tactics for using standard JDK APIs to appropriately avoid unnecessary NullPointerExceptions and to more effectively use NullPointerExceptions to indicate unexpected nulls. There are several simple tactics to ensure that expected nulls do not lead to NullPointerException. There are also tactics available to control when a NullPointerException is thrown and what details are provided in it when an unexpected null is encountered.

Tuesday, January 19, 2021

JDK 17: Hexadecimal Formatting and Parsing

Build 3 of JDK 17 Early Access Builds includes the implementation for JDK-8251989 ("Hex formatting and parsing utility"). This newly introduced functionality for parsing and formatting hexadecimal values is encapsulated in the new class java.util.HexFormat and is the subject of this post.

Running javap against the new java.util.HexFormat class provides an easy way to see an overview of its API. The following output is generated from running javap java.util.HexFormat:

Compiled from "HexFormat.java"
public final class java.util.HexFormat {
  static final boolean $assertionsDisabled;
  public static java.util.HexFormat of();
  public static java.util.HexFormat ofDelimiter(java.lang.String);
  public java.util.HexFormat withDelimiter(java.lang.String);
  public java.util.HexFormat withPrefix(java.lang.String);
  public java.util.HexFormat withSuffix(java.lang.String);
  public java.util.HexFormat withUpperCase();
  public java.util.HexFormat withLowerCase();
  public java.lang.String delimiter();
  public java.lang.String prefix();
  public java.lang.String suffix();
  public boolean isUpperCase();
  public java.lang.String formatHex(byte[]);
  public java.lang.String formatHex(byte[], int, int);
  public <A extends java.lang.Appendable> A formatHex(A, byte[]);
  public <A extends java.lang.Appendable> A formatHex(A, byte[], int, int);
  public byte[] parseHex(java.lang.CharSequence);
  public byte[] parseHex(java.lang.CharSequence, int, int);
  public byte[] parseHex(char[], int, int);
  public char toLowHexDigit(int);
  public char toHighHexDigit(int);
  public <A extends java.lang.Appendable> A toHexDigits(A, byte);
  public java.lang.String toHexDigits(byte);
  public java.lang.String toHexDigits(char);
  public java.lang.String toHexDigits(short);
  public java.lang.String toHexDigits(int);
  public java.lang.String toHexDigits(long);
  public java.lang.String toHexDigits(long, int);
  public boolean isHexDigit(int);
  public int fromHexDigit(int);
  public int fromHexDigits(java.lang.CharSequence);
  public int fromHexDigits(java.lang.CharSequence, int, int);
  public long fromHexDigitsToLong(java.lang.CharSequence);
  public long fromHexDigitsToLong(java.lang.CharSequence, int, int);
  public boolean equals(java.lang.Object);
  public int hashCode();
  public java.lang.String toString();
  static {};
}

The javap-generated listing shown above indicates that there are two static factory methods for obtaining an instance of HexFormat: HexFormat.of() and HexFormat.ofDelimiter(String). Both of these factory methods specify instances of HexFormat with "preset parameters." The remainder of the public methods are instance methods that are generally used for one of five categories of action:

  • Instruct the HexFormat instance to apply different parameters than the preset parameters the instance was instantiated with
  • Indicate configured parameters of HexFormat instance
  • Convert to and from hexadecimal representations
  • Indicate characteristics of characters and character sequences
  • Overridden Object methods: toString(), equals(Object), hashCode()

The class-level Javadoc for HexFormat's summarizes the purposes of the HexFormat class in a single sentence: "HexFormat converts between bytes and chars and hex-encoded strings which may include additional formatting markup such as prefixes, suffixes, and delimiters." That class-level Javadoc-based documentation also provides useful examples of applying the HexFormat class to covert between these types and to apply prefixes, suffixes, and delimiters. The class-level documentation further explains that the HexFormat class is "immutable and threadsafe" and is a "value-based class."

In the last version of HexFormat class source code that I saw, it was advertising "@since 16", which is one piece of evidence of the work that has been invested in this class in terms of implementation, review, and incorporated feedback (the 33 commits is another piece of evidence). The official release of HexFormat is actually JDK 17, but the JDK 17 Early Access API Documentation still shows "@since 16" as of this writing.

In this post, I provide some simple examples of applying HexFormat and these code listings are available on GitHub. Fortunately, the class-level Javadoc-based API documentation provides really good examples of applying HexFormat. I like it when classes' Javadoc shows examples of how to apply those classes and the HexFormat documentation does a good job of covering many aspects of using that class. My examples will cover a smaller portion of the class's API and is meant solely as an introduction to the basic availability of this class.

Acquiring an Instance of HexFormat

There are two static methods for acquiring an instance of HexFormat and one of those is demonstrated here:

/** Instance of {@link HexFormat} used in this demonstration. */
private static final HexFormat HEX_FORMAT_UPPER_CASE = HexFormat.of().withUpperCase();

The withUpperCase() method instructs the instance of HexFormat to "use uppercase hexadecimal characters" ("0-9", "A-F").

Converting Integers to Hexadecimal

The code snippet shown next demonstrates use of HexFormat.toHexDigits():

/**
 * Demonstrates use of {@link HexFormat#toHexDigits(int)}.
 */
public void demoIntegerToHexadecimal()
{
   for (int integerValue = 0; integerValue < 17; integerValue++)
   {
      out.println("Hexadecimal representation of integer " + integerValue + ": '"
         + HEX_FORMAT_UPPER_CASE.toHexDigits(integerValue) + "'.");
   }
}

When the above code snippet is executed, the output looks like this:

Hexadecimal representation of integer 0: '00000000'.
Hexadecimal representation of integer 1: '00000001'.
Hexadecimal representation of integer 2: '00000002'.
Hexadecimal representation of integer 3: '00000003'.
Hexadecimal representation of integer 4: '00000004'.
Hexadecimal representation of integer 5: '00000005'.
Hexadecimal representation of integer 6: '00000006'.
Hexadecimal representation of integer 7: '00000007'.
Hexadecimal representation of integer 8: '00000008'.
Hexadecimal representation of integer 9: '00000009'.
Hexadecimal representation of integer 10: '0000000A'.
Hexadecimal representation of integer 11: '0000000B'.
Hexadecimal representation of integer 12: '0000000C'.
Hexadecimal representation of integer 13: '0000000D'.
Hexadecimal representation of integer 14: '0000000E'.
Hexadecimal representation of integer 15: '0000000F'.
Hexadecimal representation of integer 16: '00000010'.

Demonstrating HexFormat.isHexDigit(int)

The following code demonstrates HexFormat.isHexDigit(int):

/**
 * Demonstrates use of {@link HexFormat#isHexDigit(int)}.
 */
public void demoIsHex()
{
   for (char characterValue = 'a'; characterValue < 'i'; characterValue++)
   {
      out.println("Is character '" + characterValue + "' a hexadecimal value? "
         + HexFormat.isHexDigit(characterValue));
   }
   for (char characterValue = 'A'; characterValue < 'I'; characterValue++)
   {
      out.println("Is character '" + characterValue + "' a hexadecimal value? "
         + HexFormat.isHexDigit(characterValue));
   }
}

Here is the output from running the above code snippet:

Is character 'a' a hexadecimal value? true
Is character 'b' a hexadecimal value? true
Is character 'c' a hexadecimal value? true
Is character 'd' a hexadecimal value? true
Is character 'e' a hexadecimal value? true
Is character 'f' a hexadecimal value? true
Is character 'g' a hexadecimal value? false
Is character 'h' a hexadecimal value? false
Is character 'A' a hexadecimal value? true
Is character 'B' a hexadecimal value? true
Is character 'C' a hexadecimal value? true
Is character 'D' a hexadecimal value? true
Is character 'E' a hexadecimal value? true
Is character 'F' a hexadecimal value? true
Is character 'G' a hexadecimal value? false
Is character 'H' a hexadecimal value? false

Demonstrating HexFormat.toString()

The HexFormat class provides an overriden version of the Object.toString() method and this is demonstrated in the following code snippet and corresponding output from running that code snippet.

/**
 * Demonstrates string representation of instance of
 * {@link HexFormat}.
 *
 * The {@link HexFormat#toString()} method provides a string
 * that shows the instance's parameters (not class name):
 * "uppercase", "delimiter", "prefix", and "suffix"
 */
public void demoToString()
{
   out.println("HexFormat.toString(): " + HEX_FORMAT_UPPER_CASE);
}
HexFormat.toString(): uppercase: true, delimiter: "", prefix: "", suffix: ""

Other Examples of HexFormat

The Javadoc-based class-level documentation for HexFormat contains more examples of how to apply this class. The examples demonstrate instantiation methods HexFormat.of() and HexFormat.ofDelimiter(String); demonstrate utility methods toHexDigit(byte), fromHexDigits(CharSequence), formatHex(byte[]), and parseHex(String); and demonstrate instance specialization methods withUpperCase() and withPrefix(String). I like that the latter examples are "realistic" examples of how operations might be used in practical situations (such as with byte fingerprints).

JDK Uses of HexFormat

The JDK and its tests already use HexFormat. The following are some examples of this.

Thursday, December 31, 2020

Java Predictions for 2021 on Foojay

Geertjan Wielenga has posted "Java Predictions for 2021" on Foojay Today. It is a collection of predictions about Java in 2021 from eight members of the Java community (Almas Baimagambetov, Stephen Chin, Brice Dutheil, Marcus Hirt, Reza Rahman, Matt Raible, Simon Ritter, and me). The predictions are concisely written and it's interesting to see the overlap between them while at the same time seeing how different parts of "Java" are important to different people. In this post, I elaborate a bit more on my predictions that were included in "Java Predictions for 2021".

I provided two somewhat related predictions for Java in 2021:

  • "Records will likely be finalized in 2021 and will be widely popular with Java developers who are fortunate enough to work on a version of the JDK with final (not preview) Record support.
  • The release of the OpenJDK 17 implementation in 2021 (which will be the foundation of Oracle's LTS version and other community members' LTS versions) will motivate many who are already working on versions of the JDK later than JDK 8 to start moving or investigate moving to JDK 17. However, JDK 8 will remain widely popular (probably will still be used by over half of Java developers), creating (in the long term) a bimodal distribution of most commonly used JDK versions (8 and 17)."

Java Records Final in 2021

The prediction that Java Records will be final in 2021 is not a risky one. Records have a been a "preview" feature in JDK 14 (JEP 359) and JDK 15 (JEP 384) and now JEP 395 "proposes to finalize the feature in JDK 16" (which is currently in Rampdown Phase One and is scheduled to be released for General Availability in March 2021). Because Records have been through two preview releases already, it seems unlikely that they won't be final as of JDK 16. In the event that they do need one more release, JDK 17 should be released in October 2021.

And Then There Were Two: JDK 8 and JDK 17

2021 will see the beginning of a move to a bimodial distribution of JDK releases most commnoly used. With JDK 17's likely release in October 2021, we'll likely see many Java shops that have already migrated to a version of JDK later than JDK 8 move to that newly released JDK 17. There have been some nice additions and improvements to OpenJDK (which is the foundation of several different JDK implementations) that have been added in recent versions of the JDK and JDK 17 will be an "LTS" (Long-term Support) release for many of the JDK implementations. As an "LTS" release, JDK 17 will appeal to Java shops that want to only be on versions with long-term support and JDK 17 will be the first since JDK 11 to have this status for many of the JDK implementations.

JDK 8 appears to still be the most widely used release of the Java even in 2020. There are several metrics and andecdotal evidence that suggest this. One example is the JetBrains 2020 Development Ecosystem survey that suggests that 75% of Java developers responding to the survey use JDK 8 (some of these developers use other versions of JDK as well) and the same chart shows 32% of responding Java developers use JDK 11. For reference, the 2019 and 2018 versions of this same survey indicated that 83% and 84% of Java developers used JDK 8 in 2019 and 2018 respectively.

JDK 8 is a version with long-term support (Oracle, for example, offers "extended support" for JDK 8 through December 2030) in several JDK implementations and some shops appear hesitant to move to JDK 9 with its introduced modularity support (and need for libraries and frameworks to support that as well). For those shops that have already migrated to a version of JDK later than JDK 8, it should be relatively easier to migrate to JDK 17. I think that some JDK 8 shops will be motivated to make the "big move" and, while doing that, will jump directly to JDK 17. However, I expect that we'll still see at least half of JDK developers still using JDK 8 even at the end of 2021. For the half of JDK users already using a version later than JDK 8 (not counting users of version of JDK before JDK 8), I think we'll begin to see them migrate to JDK 17 in 2021 and the following year or two. Within the next year or two, I expect most JDK developers will be working with JDK 8 or JDK 17.

There will, of course, be some small pockets of JDK developers using other versions before JDK 8, between JDK 8 and JDK 17 (perhaps because they use a feature or garbage collector no longer available in JDK 17), and newer versions of JDK as they are released in 2022.

"LTS" Among JDK Providers

The following are some roadmaps of various JDK vendors' JDK implementations that provide insight into each vendor's LTS concept. Although "LTS" often is referring to Oracle's plan regarding their JDK implementation built on top of OpenJDK, other JDK vendors have generally treated these "LTS" releases in similar manner.

  • AdoptOpenJDK Support and Release Roadmap
    • Shows "Java 17" as LTS.
    • States, "In addition, every three years one feature release will be designated as a Long Term Supported (LTS) release. We will produce LTS releases for at least four years."
    • States, "As a general philosophy, AdoptOpenJDK will continue to build binaries for LTS releases as long as the corresponding upstream source is actively maintained."
  • Oracle Java SE Support Roadmap
    • States, "For product releases after Java SE 8, Oracle will designate a release, every three years, as a Long-Term-Support (LTS) release. Java SE 11 is an LTS release."
  • Azul Java Support Roadmap
    • References Long Term Support (LTS) and Medium Term Support (MTS) and states, "Releases designated as LTS are those same LTS releases as designated by Oracle and the OpenJDK community."
  • Amazon Corretto
    • "Amazon Corretto 8 & 11 support extended" states, "Amazon is extending long-term support (LTS) for Amazon Corretto 8 from June 2023 to May 2026 and for Amazon Corretto 11 from August 2024 to September 2027. Long-term support (LTS) for Corretto includes security updates and specific performance enhancements released at least quarterly."

Looking Forward to 2021

Most of us are hoping for a better year in 2021 than we've experienced in 2020. The finalization of Java Records and General Availability of JDK 17 in 2021 are going to be significant positive events for Java developers and I'm hoping that these will only be a small representative sample of positive events and advancements that benefit a much wider population in 2021.

Monday, December 7, 2020

JDK 16: Stream to List In One Easy Call

As Java functional streams have become increasingly popular, an increasing number of requests is being made for new stream operations to be supported. Amidst these requests for numerous disparate new operations, one operation that seems to be requested more than the others is an operation that directly provides a List from a Stream. JDK 16 Early Access Build 27 introduces Stream.toList(), which is the subject of this post.

Before the JDK 16 Early Access Build 27 introduction of Stream.toList(), the most common approach for acquiring a List from a Stream was to invoke the approprite Collector:

stream.collect(Collectors.toList())

This is not a lot of code and it's fairly straightforward once you see it, but many have wanted an even more concise syntax for this frequently used stream operation. JDK 16 brings us this:

stream.toList()

It may be tempting to go into one's code base and use stream.toList() as a drop-in replacement for stream.collect(Collectors.toList()), but there may be differences in behavior if the code has a direct or indirect dependency on the implementation of stream.collect(Collectors.toList()) returning an ArrayList. Some of the key differences between the List returned by stream.collect(Collectors.toList()) and stream.toList() are spelled out in the remainder of this post.

The Javadoc-based documentation for Collectors.toList() states (emphasis added), "Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned..." Although there are no guarantees regarding the "type, mutability, serializability, or thread-safety" on the List provided by Collectors.toList(), it is expected that some may have realized it's currently an ArrayList and have used it in ways that depend on the characteristics of an ArrayList.

The following code snippet (full code listing on GitHub) shows a method that can be executed against the List implementations returned by Collectors.toList() and Stream.toList() to see what they have in common and how the are different.

/**
 * Analyzes the supplied {@code List} and writes to standard output
 * some key characteristics of the supplied {@code List}.
 *
 * @param listDescription Description of {@code List} to be analyzed.
 * @param listUnderAnalysis {@code List} to be analyzed.
 */
private static void analyzeList(
   final String listDescription, final List<String> listUnderAnalysis)
{
   out.println(listDescription + ": ");
   out.println("\tClass Type: " + listUnderAnalysis.getClass().getCanonicalName());
   out.println("\tAble to add to List? " + isListAddCapable(listUnderAnalysis));
   out.println("\tAble to sort List?   " + isListSortable(listUnderAnalysis));
}

When the simple analysis code above is executed against implementations of List returned by Stream.collect(Collectors.toList()) and Stream.toList(), the output appears as shown next.

Stream.collect(Collectors.toList()): 
	Class Type: java.util.ArrayList
	Able to add to List? true
	Able to sort List?   true
Stream.toList(): 
	Class Type: java.util.ImmutableCollections.ListN
	Able to add to List? false
	Able to sort List?   false
[NOT Stream] List.of(): 
	Class Type: java.util.ImmutableCollections.ListN
	Able to add to List? false
	Able to sort List?   false

The output shown above demonstrates that Stream.toList() provides a List implementation that is immutable (type ImmutableCollections.ListN that cannot be added to or sorted) similar to that provided by List.of() and in contrast to the mutable (can be changed and sorted) ArrayList provided by Stream.collect(Collectors.toList()). Any existing code depending on the ability to mutate the ArrayList returned by Stream.collect(Collectors.toList()) will not work with Stream.toList() and an UnsupportedOperationException will be thrown.

Although the implementation nature of the Lists returned by Stream.collect(Collectors.toList()) and Stream.toList() are very different, they still both implement the List interface and so they are considered equal when compared using List.equals(Object). This is demonstrated in the full code listing on GitHub.

The addition of method toList() to the Stream interface is a small thing, but it does make an often-used technique more convenient.

Saturday, December 5, 2020

Java's String.repeat Method in Action: Building PreparedStatement with Dynamic Number of Parameters

Java's String.repeat(int) method is an example of a "small" addition to Java (introduced with JDK 11) that I find myself frequently using and appreciating. This post describes use of JDK 11-introduced String.repeat(int) for easier custom generation of SQL WHERE clauses with the appropriate number of "?" parameter placeholders for use with PreparedStatements.

Many Java developers do not need to manually build PreparedStatements with the approprite number of parameter placeholders because they take advantage of a JPA implementation, other ORM framework, or library that handles it for them. However, the demonstrations in this post show how String.repeat(int) can make light work of any implementation that needs to build up a string with a specified number of repeated pieces.

 

Building SQL IN Condition with Dynamic Number of Parameters

A common approach used in Java applications for building a custom SQL SELECT statement that queries a particular database column against a collection of potential values is to use the IN operator and pass all potential matching values to that IN operator.

One Java implementation approach for building the IN operator portion of the SQL SELECT's WHERE clause is to iterate the same number of times as there are parameters for the IN operator and to use a conditional within that loop to determine the how to properly add that portion of the in-progress IN portion. This is demonstrated in the next code listing:

/**
 * Demonstrates "traditional" approach for building up the
 * "IN" portion of a SQL statement with multiple parameters
 * that uses a conditional within a loop on the number of
 * parameters to determine how to best handle each.
 *
 * @param columnName Name of database column to be referenced
 *    in the "IN" clause.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "IN" portion of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateInClauseTraditionallyOne(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder inClause = new StringBuilder();
   inClause.append(columnName + " IN (");
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      if (placeholderIndex != numberPlaceholders-1)
      {
         inClause.append("?, ");
      }
      else
      {
         inClause.append("?");
      }
   }
   inClause.append(")");
   return inClause.toString();
}

A second traditional approach for building up the IN clause to use a dynamic number of parameter placeholders is to again loop the same number of times as there are parameters, but append exactly the same new text each iteration. After iteration is completed, the extra characters are chopped off the end. This approach is shown in the next code listing:

/**
 * Demonstrates "traditional" approach for building up the
 * "IN" portion of a SQL statement with multiple parameters
 * that treats each looped-over parameter index the same and
 * the removes the extraneous syntax from the end of the
 * generated string.
 *
 * @param columnName Name of database column to be referenced
 *    in the "IN" clause.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "IN" portion of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateInClauseTraditionallyTwo(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder inClause = new StringBuilder();
   inClause.append(columnName + " IN (");
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      inClause.append("?, ");
   }
   inClause.delete(inClause.length()-2, inClause.length());
   inClause.append(")");
   return inClause.toString();
}

JDK 11 introduced a set of useful new String methods that include String.repeat(int). The String.repeat(int) method boils these approaches for generating a custom IN operator with dynamic number of parameter placeholders to a single line as shown in the next code listing:

/**
 * Demonstrates JDK 11 {@link String#repeat(int)} approach
 * for building up the "IN" portion of a SQL statement with
 * multiple parameters.
 *
 * @param columnName Name of database column to be referenced
 *    in the "IN" clause.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "IN" portion of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateInClauseWithStringRepeat(
   final String columnName, final int numberPlaceholders)
{
   return columnName + " IN (" + "?, ".repeat(numberPlaceholders-1) + "?)";
}

With the use of String.repeat(int), a single line accomplishes the task at hand and there's no need for explicit looping or explicit instantiation of a StringBuilder.

 

Building SQL OR Conditions with Dynamic Number of Parameters

Multiple SQL or conditions can be used instead of IN to test against multiple values. This is a must if, for example, the number of paramaters is over 1000 and you're using an Oracle database that only allows IN to support up to 1000 elements.

As with use of the IN condition, two commonly used approaches for building up the OR conditions for a dynamic number of parameter placeholders are to either to loop with a condition checking that each entry's output is written correctly as it's written or to remove extraneous characters after looping. These two approaches are shown in the next code listing:

/**
 * Demonstrates "traditional" approach for building up the
 * "OR" portions of a SQL statement with multiple parameters
 * that uses a conditional within a loop on the number of
 * parameters to determine how to best handle each.
 *
 * @param columnName Name of database column to be referenced
 *    in the "OR" clauses.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "OR" portions of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateOrClausesTraditionallyOne(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder orClauses = new StringBuilder();
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      if (placeholderIndex != numberPlaceholders-1)
      {
         orClauses.append(columnName).append(" = ? OR ");
      }
      else
      {
         orClauses.append(columnName).append(" = ?");
      }
   }
   return orClauses.toString();
}

/**
 * Demonstrates "traditional" approach for building up the
 * "OR" portions of a SQL statement with multiple parameters
 * that treats each looped-over parameter index the same and
 * the removes the extraneous syntax from the end of the
 * generated string.
 *
 * @param columnName Name of database column to be referenced
 *    in the "OR" clauses.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "OR" portions of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateOrClausesTraditionallyTwo(
   final String columnName, final int numberPlaceholders)
{
   final StringBuilder orClauses = new StringBuilder();
   for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
   {
      orClauses.append(columnName + " = ? OR ");
   }
   orClauses.delete(orClauses.length()-4, orClauses.length());
   return orClauses.toString();
}

The use of String.repeat(int) makes this easy as well:

/**
 * Demonstrates JDK 11 {@link String#repeat(int)} approach
 * for building up the "OR" portions of a SQL statement with
 * multiple parameters.
 *
 * @param columnName Name of database column to be referenced
 *    in the "OR" clauses.
 * @param numberPlaceholders Number of parameters for which
 *    placeholder question marks ("?") need to be added.
 * @return The "OR" portions of a SQL statement with the
 *    appropriate number of placeholder question marks.
 */
public String generateOrClausesWithStringRepeat(
   final String columnName, final int numberPlaceholders)
{
   final String orPiece = columnName + " = ? OR ";
   return orPiece.repeat(numberPlaceholders-1) + columnName + " = ?";
}

 

Conclusion

The introduction of String.repeat(int) makes it easer for Java developers to implement custom generation of Java Strings that consist of dynamically repeated portions.

All code snippets shown in this post are available on GitHub.

Saturday, November 28, 2020

JDK 16: Checking Indexes and Ranges of Longs

In my last post, I described the day period support added with JDK 16 Early Access Build 25. That same build also added methods for checking indexes and ranges of long values, which is the subject of this post.

JDK-8255150 ("Add utility methods to check long indexes and ranges") is the Enhancement used to add utility methods for checking long indexes and ranges similar to what JDK-8135248 ("Add utility methods to check indexes and ranges") added for integers with JDK 9. JDK-8255150 states, "The goal is to add a similar set of methods [as JDK-8135248] but rather than operate on int arguments, the new methods operate on long arguments."

JDK-8255150 lists the method signatures for the three new methods being added to the Objects class (descriptions provided by JDK-8135248):

  • Checking whether an index is within bounds:
    public static long checkIndex(long index, long length)
  • Checking whether an absolute range is within bounds:
    public static long checkFromToIndex(long fromIndex, long toIndex, long length)
  • Checking whether a relative range is within bounds:
    public static long checkFromIndexSize(long fromIndex, long size, long length)

Because these new methods "mirror the int utility methods," it is useful to look at JDK-8135248 to see more historical context for the justification for the introduction of these methods. That Enhancement states, "There are numerous methods in the JDK that check if an index or an absolute/relative range is valid before accessing the contents of an array (or in general a memory region for the case of a direct java.nio.ByteBuffer). ... Such checks, while not difficult, are often easy to get wrong and optimize correctly, thus there is a risk to the integrity and security of the runtime."

JDK-8135248 also talks about possibilities for optimization, "A further desire for such methods is some or all can be made intrinsic (see JDK-8042997), thus hinting to the HotSpot runtime compiler to use unsigned comparisons and better optimize array access (via aaload/store or Unsafe) in loops (especially those that are unrolled)."

A class that demonstrates these newly added methods, LongIndexRangeChecksDemo, is available on GitHub. All examples in this class demonstrate the various checks throwing IndexOutOfBoundsExceptions to indicate that the proposed index and/or size values do not fall within the allowed range. The main(String[]) function executes all the example methods and its output is divided into described sections below.

checkIndex Example Output

The message associated with this example clearly describes the index that is out of bounds and how that index is out of bounds.

==========================
== checkIndex Exception ==
==========================
java.lang.IndexOutOfBoundsException: Index 7 out of bounds for length 5
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:88)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:412)
	at java.base/java.util.Objects.checkIndex(Objects.java:435)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckIndexException$0(LongIndexRangeChecksDemo.java:34)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckIndexException(LongIndexRangeChecksDemo.java:33)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:115)

checkFromToIndex Example Output

The message clearly indicates that the range specified by the "from" and "to" indexes is too large for the expected length capacity. Note that the "[" opening the range description indicates "inclusive" and the ")" ending the range description indicates "exclusive".

================================
== checkFromToIndex Exception ==
================================
java.lang.IndexOutOfBoundsException: Range [2, 6) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:94)
	at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:459)
	at java.base/java.util.Objects.checkFromToIndex(Objects.java:461)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromToIndexException$1(LongIndexRangeChecksDemo.java:48)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromToIndexException(LongIndexRangeChecksDemo.java:47)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:116)

checkFromIndexSize Example Output

This example indicates that the range formed by a "from" index and range size is out of bounds for the specified length capacity.

==================================
== checkFromIndexSize Exception ==
==================================
java.lang.IndexOutOfBoundsException: Range [2, 2 + 6) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromIndexSize(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.checkFromIndexSize(Preconditions.java:507)
	at java.base/java.util.Objects.checkFromIndexSize(Objects.java:487)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromIndexSizeException$2(LongIndexRangeChecksDemo.java:62)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromIndexSizeException(LongIndexRangeChecksDemo.java:61)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:117)

checkFromIndexSize Overflow Example Output

This example indicates that the range formed by a "from" index and range size are out of bounds because a numeric overflow occurred when adding the size to the initial index. This is a nice catch because an overly simplistic homegrown approach that checked that the supplied initial index and supplied size are both positive and then checked the sum of the index and size against the allowed length would be faulty logic due to the overflow possibility.

=============================================
== checkFromIndexSize (Overflow) Exception ==
=============================================
java.lang.IndexOutOfBoundsException: Range [2, 2 + 9223372036854775807) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromIndexSize(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.checkFromIndexSize(Preconditions.java:507)
	at java.base/java.util.Objects.checkFromIndexSize(Objects.java:487)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromIndexSizeExceptionOnOverflow$3(LongIndexRangeChecksDemo.java:77)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromIndexSizeExceptionOnOverflow(LongIndexRangeChecksDemo.java:76)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:118)

Common Uses

The greatest beneficiary of these newly added long-supporting methods may be the authors, maintainers, and users of the foreign memory access API as described in this mailing list message: "We have to jump through quite a few hoops in the implementation of the foreign memory access API in order to leverage the intrinsification of int-based index checks, and even then we are not covering the cases where the numbers are larger than ints. Looking forward to being able to remove those hacks!"

A common use of these methods is likely to be as method guards for checking method parameters against expected preconditions similar to how other Objects' methods such as checkIndex(int, int), checkFromToIndex(int, int, int), checkFromIndexSize(int, int, int), requireNonNull(T), and requireNonNull(T, String) are used.