Concatenate two or more optional string in Java 8

不问归期 提交于 2019-12-18 06:13:16

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!