【问题标题】:Concatenate two or more optional string in Java 8在 Java 8 中连接两个或多个可选字符串
【发布时间】:2018-03-10 10:20:08
【问题描述】:

我有一个相当简单的问题要问你们。在 Java 8 中引入了 Optional 类型。我有两个 Optional<String> 类型的对象,我想知道哪种方式更优雅地连接它们。

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 */;

具体来说,如果两个原始Optional&lt;String&gt; 对象之一等于Optional.empty(),我希望整个串联也为空。

请注意,我不是在问如何在 Java 中连接两个 Optionals 的评估,而是如何连接在一些 Optional 中的两个 Strings。

提前致谢。

【问题讨论】:

    标签: java string java-8 concatenation optional


    【解决方案1】:

    您可以流式传输Optionals 并使用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);
    

    如果您使用的是 Java 8,请将 flatMap 运算符替换为 filter(Optional::isPresent).map(Optional::get)

    考虑使用joining 收集器:这将返回String,而不是Optional&lt;String&gt;

    【讨论】:

      【解决方案2】:

      这是另一种漂亮的方式:

      @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("");
          }
      }
      

      请注意,正如在其他 cmets 中所提到的,concat() 方法在不使用 StringBuilder 的情况下执行字符串连接(如果您多次调用该方法,这可能不会有效)。为了解决这个问题,在上面的示例中,我们使用了 Immutables 的 [1] @Value.Lazy,它确保 concat() 方法被调用一次,结果被缓存以供进一步调用。效果很好!

      [1]https://immutables.github.io

      【讨论】:

      • 这是一个很好的例子,但它忽略了原始问题的一个重要点:if one of the two original Optional&lt;String&gt; objects was equal to Optional.empty(), I want the whole concatenation to be empty too.
      【解决方案3】:

      我找到的解决方法如下:

      first.flatMap(s -> second.map(s1 -> s + s1));
      

      可以使用专用方法进行清洁,例如:

      first.flatMap(this::concat);
      Optional<String> concat(String s) {
          second.map(s1 -> s + s1);
      }
      

      但是,我认为可以找到更好的东西。

      如果我们想泛化为Optional&lt;String&gt;的列表或数组,那么我们可以使用类似下面的东西。

      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));
      }
      

      注意,为了编译上面的代码,我们必须手动将Optional.empty()的类型变量绑定到String

      【讨论】:

      • 除非你有一个半群二元运算符,否则 flatmap 是结合两个选项的最佳方式。就像在 scala 中一样,你可以这样做 first |+| second
      • 不,这是你在 Java 8 中能做到的最好的。
      • 所以,你是说我不能做得更好:(你能解释一下“半群”的事情吗?
      • 非常感谢您的链接。
      • 如果first 为空而second 不是,则first.flatMap(s -> second.map(s1 -> s + s1)) 将返回空,这是不正确的。也就是说:Optional.empty().flatMap(s -&gt; Optional.of("second").map(s1 -&gt; s + s1))会返回empty(),而不是"second"
      【解决方案4】:
      @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")));
      }
      

      这是一个在 Stream 上使用 reduce 方法的实现。

      【讨论】:

      • 你知道每次你使用@SafeVarargs 都会有一只小猫死在某个地方吗?
      • 我用您解决方案的更安全变体更新了我的答案。看看吧。
      • 我猜周围有很多死猫,因为@SafeVarargs 也用于Arrays.asList(T... a) 之类的东西。如果它与您有关,您总是可以接受 Collection 或 Stream 作为方法的参数...
      • 说真的,来自编译器的警告通常意味着代码中的问题。 Java 是一种强类型的编程语言,您应该避免任何类型定义不明确的情况。这不是我说的,是 Effective Java 中的 Joshua Bloch。
      【解决方案5】:

      任何需要灵活数量的可选字符串的解决方案都必须明确使用StringBuilder,而不是依赖编译器为您生成一个。

      String concatThem(Stream<String> stringsin) {
          StringBuilder sb = new StringBuilder();
          stringsin.forEach(s -> sb.append(s));
          return sb.toString();
      }
      

      如果你有一个Stream&lt;Optional&lt;String&gt;&gt;,那么它会变成:

      String concatThem(Stream<Optional<String>> stringsin) {
          StringBuilder sb = new StringBuilder();
          stringsin.filter(Optional::isPresent).forEach(s -> sb.append(s.get()));
          return sb.toString();
      }
      

      否则,如果您有 N 个可选字符串,您最终会经历创建和销毁 N-1 个一次性 StringBuilder 对象(在编译时生成)和 N-1 个字符串的繁重循环。

      编辑:我读错了,所以如果其中任何一个丢失了,请按照以下方法清除所有内容:

      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();
      }
      

      当然,如果您坚持使用语法轻而执行繁重的新 API。

      【讨论】:

      • 您知道您的解决方案不遵守重要的基本约束吗?如果任何Strings 为空,则结果String 也应该为空。无论如何,我认为有一种更实用的方式可以通过。
      • 看看我更新的答案。我使用了一种更实用、更惯用的方法。
      【解决方案6】:

      你可以使用类似的东西:

      Optional<String> result;
      result = first.isPresent() && second.isPresent() ?  Optional.of(first.get() + second.get()) : Optional.empty();
      

      【讨论】:

      • 还考虑了只有一个不存在的场景
      • @ds011591 是,条件是&amp;&amp;
      • 所以如果一个而不是另一个,那么什么都不返回?我认为您希望返回当时存在的值。
      • @ds011591 请注意,问题本身显示为 详细地说,如果两个原始 Optional 对象之一等于 Optional.empty(),我希望整个串联为空也是。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-14
      • 1970-01-01
      • 1970-01-01
      • 2020-09-25
      • 2014-05-20
      相关资源
      最近更新 更多