简体   繁体   中英

How to join two Optional<String> with a delimiter in Java 8

I have two Optional strings, name1 and name2 . I want to join the two such that the result is also an Optional with:

  1. If either one is non-empty, the result should be the non-empty name.
  2. If both are non-empty, I want the result to be joined with the delimiter AND .
  3. If both are empty, the result should be an empty Optional

My attempt at this:

StringBuilder sb = new StringBuilder();
name1.ifPresent(sb::append);
name2.ifPresent(s -> {
    if (sb.length() > 0) {
        sb.append(" AND ");
    }
    sb.append(s);
}
Optional<String> joinedOpt = Optional.ofNullable(Strings.emptyToNull(sb.toString()));

This works, but seems ugly and not very functional.

PS: There is a similar question but the accepted answer is wrong. Specifically, if name1 is empty and name2 is not, it returns an empty optional.

One solution is to stream and reduce() :

Optional<String> joinedOpt = Stream.of(name1, name2)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .reduce((a, b) -> a + " AND " + b);

Feel free to replace the filter/map combo with Java 9 or Guava as others have suggested.

Possible solution in Java 1.9 would be

 Optional<String> name1 = Optional.of("x");
 Optional<String> name2 = Optional.of("y");
 String s = Stream.concat(name1.stream(), name2.stream()).collect(Collectors.joining(" AND "));
 System.out.println(s);

With Java 1.8 there is no way that you can go from an Optional to a Stream. However, you can add such a conversion quite easily to Java 1.8.

static <T> Stream<T> of(Optional<T> option) {
    return option.map(Stream::of).orElseGet(Stream::empty);
}

And now you can use the of method to concatenate both streams.

Now, to get an empty Optional if there is no result, you can wrap the result into an Optional and do a simple filter.

Optional<String> result = Optional.of(collect).filter(Optional::isPresent);

Slightly different flavor using Collectors.joining :

    Optional<String> result  = Optional.of( 
        Stream.of(so1, so2)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect( Collectors.joining(" AND ") ) 
    ).filter( s -> !s.isEmpty() ); 

What I like about Collectors.joining is that Java streams can potentially reuse single StringBuilder class instead of creating a new one when doing + operation.

Sometimes, trying to get functional with Optional (or Stream ) just obscures what you are trying to express in your code.

To me, the main idea here is that if both values are present, they should be concatenated with " AND ". Here is an attempt to focus on that:

Optional<String> joinedOpt;
if (name1.isPresent() && name2.isPresent()) {
    joinedOpt = Optional.of(name1.get() + " AND " + name2.get());
} else {
    joinedOpt = name1.isPresent() ? name1 : name2;
}

I might be misinterpreting his message, but this approach was inspired by Stuart Marks' answer to a similiar question, which he discussed in more depth in his Devoxx talk about the choices he made as he helped design Optional .

Close to the accepted answer

Optional<String> joinedOpt = Stream.of(name1, name2)
            .flatMap(x -> x.map(Stream::of).orElse(null))
            .reduce((a, b) -> a + " AND " + b);

Or in java-9:

Optional<String> joinedOpt = Stream.of(name1, name2)
            .flatMap(Optional::stream)
            .reduce((a, b) -> a + " AND " + b);

Here is a streaming solution, which filters for those Optional s that are present, then extracts the elements and uses a built-in Collector to join the remaining elements. Then it tests if the result is empty at the end to return an empty Optional .

It accepts a List of Optional s so it covers a more general case than just two elements.

public Optional<String> optionalJoin(List<Optional<String>> strings) {
    String result = strings.stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.joining(" AND "));
    return result.isEmpty() ? Optional.empty() : Optional.of(result);
}

One-liner without Streams, but not especially elegant:

    public static Optional<String> concat(Optional<String> name1, Optional<String> name2) {
        return Optional.ofNullable(name1.map(s1 -> name2.map(s2 -> s1 + " AND " + s2).orElse(s1)).orElse(name2.orElse(null)));
    }

    public static void main(String[] args) {
        System.out.println(concat(Optional.of("A"), Optional.of("B")));
        System.out.println(concat(Optional.of("A"), Optional.empty()));
        System.out.println(concat(Optional.empty(), Optional.of("B")));
        System.out.println(concat(Optional.empty(), Optional.empty()));
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM