Monday, February 23, 2015

Joining Strings in JDK 8

JDK 8 introduced language features such as lambda expressions, streams, and even the new Date/Time API that will change the way we write Java applications. However, there are also several new APIs and features that might be less "game changing," but still bring greater convenience and expressiveness to the Java programming language. In this post, I look at one of these smaller features and examine the ability to easily concatenate multiple Strings in JDK 8.

Perhaps the easiest way to concatenate multiple Strings in JDK 8 is via two new static methods on the ubiquitous Java class String: join(CharSequence, CharSequence...) and join(CharSequence, Iterable). The next two code listings demonstrate how easy it is to apply these two String.join methods.

Using String.join(CharSequence, CharSequence...)
/**
 * Words associated with the blog at http://marxsoftware.blogspot.com/ in array.
 */
private final static String[] blogWords = {"Inspired", "by", "Actual", "Events"};

/**
 * Demonstrate joining multiple Strings using static String
 * "join" method that accepts a "delimiter" and a variable
 * number of Strings (or an array of Strings).
 */
private static void demonstrateStringJoiningArray()
{
   final String blogTitle = String.join(" ", blogWords);
   out.println("Blog Title: " + blogTitle);

   final String postTitle = String.join(" ", "Joining", "Strings", "in", "JDK", "8");
   out.println("Post Title: " + postTitle);
}
Using String.join(CharSequence, Iterable)
/**
 * Pieces of a Media Access Control (MAC) address.
 */
private final static List<String> macPieces;

static
{
   macPieces = new ArrayList<>();
   macPieces.add("01");
   macPieces.add("23");
   macPieces.add("45");
   macPieces.add("67");
   macPieces.add("89");
   macPieces.add("ab");
};

/**
 * Demonstrate joining multiple Strings using static String
 * "join" method that accepts a "delimiter" and an Iterable
 * on Strings.
 */
private static void demonstrateStringJoiningIterable()
{
   final String macAddress = String.join(":", macPieces);
   out.println("MAC Address: " + macAddress);
}

The output from running the two above code listings is:

Blog Title: Inspired by Actual Events
Post Title: Joining Strings in JDK 8
MAC Address: 01:23:45:67:89:ab

Using the two static String.join methods is an easy way to combine strings, but the StringJoiner class introduced with JDK 8 provides even more power and flexibility. The next code listing demonstrates instantiating a StringJoiner and passing it a specified delimiter (decimal point), prefix (opening parenthesis), and suffix (closing parenthesis).

Simple String Joiner Use
/**
 * Demonstrate joining multiple Strings using StringJoiner
 * with specified prefix, suffix, and delimiter.
 */
private static void demonstrateBasicStringJoiner()
{
   // StringJoiner instance with decimal point for delimiter, opening
   // parenthesis for prefix, and closing parenthesis for suffix.
   final StringJoiner joiner = new StringJoiner(".", "(", ")");
   joiner.add("216");
   joiner.add("58");
   joiner.add("216");
   joiner.add("206");
   final String ipAddress = joiner.toString();
   out.println("IP Address: " + ipAddress);
}

Running the above code prints the following string to standard output: "IP Address: (216.58.216.206)"

The StringJoiner is an especially attractive approach in the scenario where one is adding delimiting characters to a String being built up as part of some type of iteration with a StringBuilder. In such cases, it was often necessary to remove an extra character added to the end of that builder with the last iteration. StringJoiner is "smart enough" to only add the delimiters between strings being concatenated and not after the final one. The successive calls to add(CharSequence) methods look very similar to the StringBuilder/StringBuffer APIs.

The final JDK 8-introduced approach for joining Strings that I will cover in this post is use of stream-powered collections with a joining collector (reduction operation). This is demonstrated in the next code listing and its output is the same as the String.join approach used to print a MAC address via the String.join that accepted an Iterable as its second argument.

String Joining with a Collection's Stream
/**
 * Demonstrate joining Strings in a collection via that collection's
 * Stream and use of the Joining Collector.
 */
private static void demonstrateStringJoiningWithCollectionStream()
{
   final String macAddress =
      macPieces.stream().map(
         piece -> piece).collect(Collectors.joining(":"));
   out.println("MAC Address: " + macAddress);
}

If a developer wants the ability to provide a prefix and suffix to the joined string without having to make successive calls to add methods required to join Strings with StringJoiner, the Collectors.joining(CharSequence, CharSequence, CharSequence) method is a perfect fit. The next code example shows the IP address example from above used to demonstrate StringJoiner, but this time implemented with a collection, stream, and joining collector. The output is the same as the previous example without the need to specify add(CharSequence) for each String to be joined.

String Joining with Collection's Stream and Prefix and Suffix
/**
 * Demonstrate joining Strings in a collection via that collection's
 * Stream and use of a Joining Collector that with specified prefix 
 * and suffix.
 */
private static void demonstrateStringJoiningWithPrefixSuffixCollectionStream()
{
   final List<String> stringsToJoin = Arrays.asList("216", "58", "216", "206");
   final String ipAddress =
      stringsToJoin.stream().map(
         piece -> piece).collect(Collectors.joining(".", "(", ")"));
   out.println("IP Address: " + ipAddress);
}

This blog post has covered three of the approaches to joining Strings available with JDK 8:

  1. Static String.join methods
  2. Instance of StringJoiner
  3. Collection Stream with Joining Collector

No comments: