1. Overview
When we work with Java, manipulating lists is a fundamental skill.
In this quick tutorial, we’ll explore different ways to modify or transform a list and then print its elements in Java.
2. Modifying and Printing a List
Printing a list of elements isn’t a challenge to us. For example, we can call the print action in the forEach() method:
List<String> theList = Lists.newArrayList("Kai", "Liam", "Eric", "Kevin");
theList.forEach(element -> log.info(element));
In the code above, we used an SLF4J logger to output elements in the given list. When we execute the code, we can see the four names are printed in the console:
Kai
Liam
Eric
Kevin
If we intend to modify the elements in the list before printing them, we can utilize the List.replaceAll() method.
Next, let’s convert each String element in theList to uppercase and print the modified values in a test method:
List<String> theList = Lists.newArrayList("Kai", "Liam", "Eric", "Kevin");
theList.replaceAll(element -> element.toUpperCase());
theList.forEach(element -> log.info(element));
assertEquals(List.of("KAI", "LIAM", "ERIC", "KEVIN"), theList);
As we can see, we use a lambda expression in the replaceAll() method to perform the case conversion. After running the test, we can see the uppercase values in the console:
KAI
LIAM
ERIC
KEVIN
It’s worth noting that the replaceAll() method requires the list object to be a mutable list, such as the Arraylist used in the above code. The method throws an UnsupportedOperationException if the list is immutable, such as the list objects returned by Collection.singletonList() and List.of().
Therefore, in practical scenarios, it’s often preferable to transform the original list into a new one rather than directly modifying it. Next, let’s explore how to transform a list and seamlessly output its elements efficiently.
3. Transforming and Printing a List Using the Stream API
The Stream API, introduced in Java 8, significantly changed the way we handle collections of objects. Streams provide a declarative and functional approach to processing data, offering a concise and expressive way to perform operations on collections.
For example, we can take a list as the source, use the map() method to transform elements in the stream, and print the elements using forEachOrdered() in this way:
theList.stream()
.map(... <the transformation logic> ...)
.forEachOrdered( ... <print the element> ...)
The code is pretty straightforward. However, it’s important to note that Stream.forEachOrdered() is a terminal operation. This terminal operation essentially marks the end of the stream pipeline. Consequently, the stream object becomes inaccessible after this method is called. This limitation implies that subsequent stream operations, such as collecting the transformed elements, are no longer feasible.
Therefore, we’d like to achieve our goal through a different approach, one that allows us to continue performing operations on the stream.
A straightforward idea is to include the printing method call in map():
List<String> theList = List.of("Kai", "Liam", "Eric", "Kevin");
List<String> newList = theList.stream()
.map(element -> {
String newElement = element.toUpperCase();
log.info(newElement);
return newElement;
})
.collect(Collectors.toList());
assertEquals(List.of("KAI", "LIAM", "ERIC", "KEVIN"), newList);
In this way, printing the stream doesn’t terminate the stream pipeline, and we can still perform a Collector operation afterward. Of course, the transformed elements are printed in the console:
KAI
LIAM
ERIC
KEVIN
However, one drawback of this approach is that it unnecessarily adds irrelevant logic to the map() method. Next, let’s improve it by employing the peek() method:
List<String> theList = List.of("Kai", "Liam", "Eric", "Kevin");
List<String> newList = theList.stream()
.map(element -> element.toUpperCase())
.peek(element -> log.info(element))
.collect(Collectors.toList());
assertEquals(List.of("KAI", "LIAM", "ERIC", "KEVIN"), newList);
Unlike forEachOrdered(), peek() is an intermediate operation. It performs the provided action on each element in the stream and returns the stream. Therefore, we can add further operations to the stream pipeline after invoking peek(), such as collect() in the above code.
The peek() method accepts a Consumer instance as the parameter. In our example, we passed a lambda expression as the Consumer to peek().
When we give this test a run, it passes, and the expected output is printed to the console:
KAI
LIAM
ERIC
KEVIN
4. Conclusion
In this article, we first demonstrated how to modify and print a list using the replaceAll() + forEach() approach. Then, we explored how to use the Stream API to transform and print elements in a stream.
As always, the complete source code for the examples is available over on GitHub.