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.

1 comment:

@DustinMarx said...

JDK 17 Early Access Builds Build #16 (31 March 2021) included JDK-8263754 ("HexFormat 'fromHex' methods should be static"), which changed some instance methods in HexFormat to be static methods instead.