I want to perform multiple tasks on a single string.
I need to get a string and extract different sub-strings using a delimiter ("/"
), then reverse the list of sub-strings and finally join them using another delimiter ("."
) such that /tmp/test/hello/world/
would turn into: world.hello.test.tmp
Using Java 7 the code is as follows:
String str ="/tmp/test/";
List<String> elephantList = new ArrayList<String>(Arrays.asList(str.split("/")));
StringBuilder sb = new StringBuilder();
for (int i=elephantList.size()-1; i>-1; i--) {
String a = elephantList.get(i);
if (a.equals(""))
{
elephantList.remove(i);
}
else
{
sb.append(a);
sb.append('.');
}
}
sb.setLength(sb.length() - 1);
System.out.println("result" + elephantList + " " + sb.toString());
I was wondering how I could do the same thing using Java 8 streams and the join function it has for Strings
Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.
With Java 8, Collection interface has two methods to generate a Stream. stream() − Returns a sequential stream considering collection as its source. parallelStream() − Returns a parallel Stream considering collection as its source.
Overview Java 8 has introduced a new Stream API that lets us process data in a declarative manner. In this quick article, we would learn how to use the Stream API to split a comma-separated String into a list of Strings and how to join a String array into a comma-separated String.
It consists of many other methods also which follow the generation of stream consisting collection those methods include: forEach, map, filter, limit, etc. How Stream Works in Java 8?
Introduced in Java 8, the Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result.
It internally uses a java.util.StringJoiner to perform the joining operation. We can also use toSet () to get a set out of stream elements: We can use Collectors.toCollection () to extract the elements into any other collection by passing in a Supplier<Collection>.
The most straightforward way is to collect the terms into a list, reverse the list and join on the new delimiter:
import static java.util.stream.Collectors.toCollection;
List<String> terms = Pattern.compile("/")
.splitAsStream(str)
.filter(s -> !s.isEmpty())
.collect(toCollection(ArrayList::new));
Collections.reverse(terms);
String result = String.join(".", terms);
You can do it without collecting into an intermediate list but it will be less readable and not worth the trouble for practical purposes.
Another issue to consider is that your strings appear to be paths. It is usually better to use Path
class rather than splitting by "/" manually. Here's how you would do this (this approach also demonstrates how to use IntStream
over indexes to stream over a list backwards):
Path p = Paths.get(str);
result = IntStream.rangeClosed(1, p.getNameCount())
.map(i -> p.getNameCount() - i) // becomes a stream of count-1 to 0
.mapToObj(p::getName)
.map(Path::toString)
.collect(joining("."));
This will have the advantage of being OS-independent.
If you do not want an intermediate list and just want to join the String
reversely:
String delimiter = ".";
Optional<String> result = Pattern.compile("/")
.splitAsStream(str)
.filter(s -> ! s.isEmpty())
.reduce((s, s2) -> String.join(delimiter, s2, s));
Or just use .reduce((s1, s2) -> s2 + '.' + s1);
as it is probably as readable as String.join(".", s2, s1);
(thanks Holger for the suggestion).
From then on you could do one of the following:
result.ifPresent(System.out::println); // print the result
String resultAsString = result.orElse(""); // get the value or default to empty string
resultAsString = result.orElseThrow(() -> new RuntimeException("not a valid path?")); // get the value or throw an exception
Another way using StreamSupport
and Spliterator
(inspired by Mishas suggestion to use a Path
):
Optional<String> result = StreamSupport.stream(Paths.get(str).spliterator(), false)
.map(Path::getFileName)
.map(Path::toString)
.reduce((s, s2) -> s2 + '.' + s);
Of course you can simplify it by omitting the intermediate Optional
-object and just call your desired method immediately:
stream(get(str).spliterator(), false)
.map(Path::getFileName)
.map(Path::toString)
.reduce((s, s2) -> s2 + '.' + s)
.ifPresent(out::println); // orElse... orElseThrow
in the last example you would add the following static imports:
import static java.lang.System.out;
import static java.nio.file.Paths.get;
import static java.util.stream.StreamSupport.stream;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With