Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Streams for String manipulation

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

like image 990
Hossein Avatar asked Feb 10 '17 05:02

Hossein


People also ask

Does Java 8 support streams?

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.

What are two types of streams in Java 8?

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.

What is stream in Java 8?

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.

What are the methods of stream in Java?

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?

What is stream API in Java?

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.

How to get a set out of stream elements in Java?

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>.


2 Answers

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.

like image 142
Misha Avatar answered Mar 02 '23 22:03

Misha


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;
like image 31
Roland Avatar answered Mar 03 '23 00:03

Roland