问题
I have a rather simple question for you guys. In Java 8 it was introduced the Optional type. I have two objects of type Optional<String> and I want to know which is the more elegant way to concatenate them.
Optional<String> first = Optional.ofNullable(/* Some string */);
Optional<String> second = Optional.ofNullable(/* Some other string */);
Optional<String> result = /* Some fancy function that concats first and second */;
In detail, if one of the two original Optional<String> objects was equal to Optional.empty(), I want the whole concatenation to be empty too.
Please, note that I am not asking how to concatenate the evaluation of two Optionals in Java, but how to concatenate two Strings that are inside some Optional.
Thanks in advance.
回答1:
The solution I found is the following:
first.flatMap(s -> second.map(s1 -> s + s1));
which can be cleaned using a dedicated method, such the following:
first.flatMap(this::concat);
Optional<String> concat(String s) {
second.map(s1 -> s + s1);
}
However, I think that something better can be found.
If we want to generalize to a list or an array of Optional<String>, then we can use something similar to the following.
Optional<String> result =
Stream.of(Optional.of("value1"), Optional.<String>empty())
.reduce(Optional.of(""), this::concat);
// Where the following method id used
Optional<String> concat(Optional<String> first, Optional<String> second) {
return first.flatMap(s -> second.map(s1 -> s + s1));
}
Note that in order to compile the above code, we have to manually bind the type variable of Optional.empty() to String.
回答2:
@SafeVarargs
public final Optional<String> concat(Optional<String>... inputs)
{
return Arrays.stream(inputs)
.reduce((left, right) -> left.flatMap(leftValue -> right.map(rightValue -> leftValue + rightValue)))
.get();
}
@Test
public void shouldReturnEmptyIfFirstItemIsEmpty()
{
assertThat(concat(Optional.empty(), Optional.of("B")), is(Optional.empty()));
}
@Test
public void shouldReturnEmptyIfSecondItemIsEmpty()
{
assertThat(concat(Optional.of("A"), Optional.empty()), is(Optional.empty()));
}
@Test
public void shouldConcatIfNoItemIsEmpty()
{
assertThat(concat(Optional.of("A"), Optional.of("B")), is(Optional.of("AB")));
}
Here's an implementation using the reduce method on Stream.
回答3:
You can use something like :
Optional<String> result;
result = first.isPresent() && second.isPresent() ? Optional.of(first.get() + second.get()) : Optional.empty();
回答4:
Any solution that requires a flexible number of optional strings must explicitly use a StringBuilder, rather than rely on the compiler to generate one for you.
String concatThem(Stream<String> stringsin) {
StringBuilder sb = new StringBuilder();
stringsin.forEach(s -> sb.append(s));
return sb.toString();
}
If you have a Stream<Optional<String>> then it becomes:
String concatThem(Stream<Optional<String>> stringsin) {
StringBuilder sb = new StringBuilder();
stringsin.filter(Optional::isPresent).forEach(s -> sb.append(s.get()));
return sb.toString();
}
Otherwise if you have N optional strings you end-up with a heavy cycle of creation and destruction of N-1 single-use StringBuilder objects (generated at compile time) and N-1 strings.
Edit: I had misread, so here's how to do it if any of them is missing to clear it all:
String concatThem(Stream<Optional<String>> stringsin) {
StringBuilder sb = new StringBuilder();
try {
stringsin.forEach(s -> {
if (!s.isPresent()) throw new IllegalArgumentException();
sb.append(s.get())
});
}
catch(IllegalArgumentException ex) {
sb.setLength(0);
}
return sb.toString();
}
This is of course if you insist on using the new API that's light on the syntax and heavy on the execution.
回答5:
Here's another pretty way:
@Value.Immutable
public abstract class Person {
public Optional<String> firstName() {
return Optional.of("John");
}
public Optional<String> lastName() {
return Optional.of("Smith");
}
public Optional<String> location() {
return Optional.of("Paris");
}
@Value.Lazy
public String concat() {
return Stream.of(firstName(), lastName(), location())
.filter(Optional::isPresent)
.map(Optional::get)
.filter(StringUtils::isNotBlank)
.reduce((first, second) -> first + '.' + second)
.orElse("");
}
}
Note that, as mentioned in other comments, the concat() method performs string concatenations without using a StringBuilder (which might not be performant if you call the method a lot of times). To fix this, in the above example we're using Immutables' [1] @Value.Lazy, which makes sure the concat() method is called once and the result is cached for further calls. Works great!
[1] https://immutables.github.io
回答6:
You can stream the Optionals and reduce them with a concat.
Optional<String> first = Optional.of("foo");
Optional<String> second = Optional.of("bar");
Optional<String> result = Stream.of(first, second).flatMap(Optional::stream).reduce(String::concat);
If you are using Java 8 replace the flatMap operator with filter(Optional::isPresent).map(Optional::get).
Consider also to use the joining collectors: this will return String, not an Optional<String>.
来源:https://stackoverflow.com/questions/46473098/concatenate-two-or-more-optional-string-in-java-8