Concatenating Strings in Java 8
This walkthrough of string concatenation explores the additions Java 8 made as well as how to use it with relatively new classes, like Optional.
Join the DZone community and get the full member experience.
Join For FreeHave you ever experienced the need to build a comma separated list of values from a list or an array in Java or building a file path using concatenated folder names and a file path delimiter? Well, Java 8 has this written for you— and not to only reduce your development time or prevent the code from additional errors, but to let you write more readable and understandable code.
Imagine a simple situation where we want to build a comma separated list of values from a list like the one here:
List<String> commaSeparatedValues = Arrays.asList("value1", "value2", "value3");
Let's do it a bit differently and replace the comma symbol with a semicolon. What we did until Java 8 was iterate over the list and append using the well-known Java class StringBuilder, or StringBuffer based on the use case. Normally, the code is implemented in a separate utility method that has to be tested by a unit test in order to validate that it produces the right result — and also to keep the code coverage in a good state.
Well, Java 8 offers static methods as part of the String class definition intended exactly for this purpose:
List<String> valuesList = Arrays.asList("value1", "value2", "value3");
String commaSeparatedValues = String.join("; ", valuesList);
System.out.println(commaSeparatedValues);
>>Output: value1; value2; value3
The join method offers the convenience of not adding the delimiter at the end of the string as well. As a matter of fact, class String offers two join methods with the following signatures:
public static String join(CharSequence delimiter, CharSequence... elements)
public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
Furthermore, an important part of the method's contract is that the parameters passed can't be null and the method will throw a NullPointerException if the contract is violated — if one or both of the parameters is null.
Another simple use case is file path construction in Java, when we need to concatenate various folder names:
String baseDirectory = "baseDir";
String subFolder = "subFolder";
String fileName = "fileName";
List<String> filePathParts = Arrays.asList(baseDirectory, subFolder, fileName);
File file = new File(String.join(File.separator, filePathParts));
All good so far, but what happens when the String's join method is called? It delegates the calls to a slightly more sophisticated class called StringJoiner, which in fact offers more than just concatenating strings with a parametrized delimiter. As the Javadoc states
StringJoiner is used to construct a sequence of characters separated
by a delimiter and optionally starting with a supplied prefix
and ending with a supplied suffix.
If we want to use StringJoiner with a suffix and prefix, we will have to use it directly or wrapped by another class. People experienced with Java 8 Streams most probably know where we're going with this: the Collectors class:
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
Although the implementation of the method looks a bit complicated, using it with Java 8 Streams is pretty straightforward. Imagine we have to build a file path to store files based on predefined folder names — but also using parametrized names like, for example, the file name must end up with a particular date, and one of the folders should have a parametrized name not defined during the initial initialization of the class responsible for constructing the full file path:
String suffix = new SimpleDateFormat("yyyyMMdd").format(new Date());
String paramValue = "P";
List<String> filePathParts = Arrays.asList(baseDirectory, subFolder, "%s", fileName);
String filePath = filePathParts.stream().map(value -> String.format(value, paramValue))
.collect(Collectors.joining(File.separator, "", suffix));
>>Output: baseDir\subFolder\P\fileName20170804
You have probably noticed that, for each element, we apply string-based formatting before joining the elements. This can also be done after the concatenated string was built:
String filePathFormatted = String.format(filePathParts.stream()
.collect(Collectors.joining(File.separator, "", suffix)), paramValue);
So far, we managed to build a list of concatenated values and a file path with only a few lines. What about converting Java objects to concatenated strings in order to persist them as rows in files? Here is our class that can be persisted using the same approach:
public class Person {
private String name;
private String address;
private int age;
public Person(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public String convertToRow(CharSequence delimiter) {
return String.join(delimiter, Arrays.asList(name, address, String.valueOf(age)));
}
}
And the usage of it will look like the following:
Person person = new Person("John Right", "London", 35);
System.out.println(person.convertToRow(","));
>>Output: John Right,London,35
From a code design perspective, the persistence method might be placed in another class responsible for Person's persistence and provide more flexibility regarding column ordering and formatting in the form of persistence metadata.
The intent of this post was to show the Java 8 approach for string value concatenation and its usage in various use cases. We definitely wrote less code, we didn't need unit testing for the concatenation part, we were able to combine it with additional processing of elements to be concatenated, and we managed to implement a simple object's persistence with one row of code. For more complicated objects, the building of the list of values for persistence may be extracted to a separate method, allowing the use of various strategies for the object's field ordering and formatting.
Opinions expressed by DZone contributors are their own.
Comments