I have two Optional
strings, name1
and name2
. I want to join the two such that the result is also an Optional with:
AND
. 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.