如何在Java 8中使用分隔符连接两个Optional<String>?

12

我有两个Optional字符串,分别是name1name2。我想将它们合并成一个Optional,满足以下条件:

  1. 如果其中一个字符串不为空,则结果应为非空的那个字符串。
  2. 如果两个字符串都不为空,我想用分隔符AND将它们拼接起来。
  3. 如果两个字符串都为空,则结果应为一个空的Optional

我的尝试:

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

这个方法可以运行,但看起来不太美观并且功能也不够完善。

顺便说一下:这里有一个类似的问题,但被接受的答案是错误的。具体来说,如果name1为空而name2不为空,它会返回一个空的optional。


2
请参见 https://dev59.com/AVkS5IYBdhLWcg3wvI60#39539532。 - erickson
假设代码能正确运行,你可能想以更完整的方式编写你的示例,并在[codereview.se]上请求评论。请确保先阅读面向Stack Overflow用户的代码审查指南,因为那里有些不同的做法! - Toby Speight
7个回答

18

一个解决方案是使用流(stream)和 reduce() 方法:

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

可以根据其他人建议的方式,自由地将过滤/映射组合替换为Java 9或Guava。


1
花了一些时间才理解这个解决方案,所以它在完成任务方面表现出色,但我认为理解起来有点费时,因此可读性很低。当我说可读性时,我指的是“另一个”人可以查看代码并说“啊,这就是它的功能”。但这可能只是我的想法... - Ousmane D.
1
@Aominè。我认为这只是个人意见。对我来说,它看起来像是一个干净且易读的解决方案。也许这是因为以前做过MapReduce的工作。我的唯一评论是关于在每个元素上创建太多中间StringBuilder对象,而不是在使用Collectors.joining时潜在地重用单个对象。 - tsolakp
1
@tsolakp 是的,正如所述,这是一个基于观点的评论。顺便说一句,我喜欢这个答案;-) 。无论如何,关于在每个元素上创建太多中间 StringBuilder 对象而不是在使用 Collectors.joining 时可能重用单个对象的唯一评论是,记住我们最多只有 2 个(加上“AND”)字符串对象,因此可能没有必要使用 StringBuilder - Ousmane D.
2
连接操作总是在幕后使用 StringBuilder。但是缩减操作最多只会被调用一次。 - shmosel
5
@shmosel 不是每次都是这样的,从9点开始。 - Eugene

7
Java 1.9 中可能的解决方案是:
 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);

在Java 1.8中,你无法从Optional转换为Stream。然而,你可以很容易地将其转换为Java 1.8。

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

现在,您可以使用of方法连接这两个流。

现在,如果没有结果,则可以将结果包装到Optional中并进行简单过滤,以获得一个空的Optional

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

1
不错!我猜可以在Java 8中使用Guava的[Streams.stream](https://google.github.io/guava/releases/21.0/api/docs/com/google/common/collect/Streams.html#stream-java.util.Optional-)来完成相同的操作。 - spinlok
@spinlok 如果有Guava可用,那么你已经排序了。 - Sleiman Jneidi
1
当它们都为空时,这不会返回一个空的可选项。 - shmosel
@SleimanJneidi 另外为什么要使用 orElseGet 而不是 orElse(Stream.empty()),毕竟创建一个空流不会很耗费性能。 - Eugene

3

使用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() ); 

我喜欢 Collectors.joining 的原因在于Java流可能会重复使用单个StringBuilder类,而不是在执行+操作时创建新的StringBuilder类。


2
有时候,在使用Optional(或者Stream)的时候,可能会让你的代码变得晦涩难懂。
对我来说,这里的主要思想是如果两个值都存在,它们应该用“ AND ”连接起来。下面是一个尝试将重点放在此处的例子:
Optional<String> joinedOpt;
if (name1.isPresent() && name2.isPresent()) {
    joinedOpt = Optional.of(name1.get() + " AND " + name2.get());
} else {
    joinedOpt = name1.isPresent() ? name1 : name2;
}

我可能对他的信息有误解,但这种方法是受到Stuart Marks在类似问题的答案启发的,在他关于他协助设计Optional时所做选择的Devoxx演讲中更深入地讨论了这个问题。


这绝对是正确的做法。也许不太花哨,但百分之百表达了意图,并且代码非常易读。 - fps
@FedericoPeraltaSchaffner 特别是如果有适当的解构模式被实现(比如在10中的switch语句),类似 name1 isPresentGet String x(这里完全是虚构的语法) - Eugene

1
接近所接受的答案。
Optional<String> joinedOpt = Stream.of(name1, name2)
            .flatMap(x -> x.map(Stream::of).orElse(null))
            .reduce((a, b) -> a + " AND " + b);

或者在Java 9中:

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

0
这里有一个流式解决方案,它过滤那些存在的Optional,然后提取元素并使用内置的Collector连接剩余的元素。然后在最后测试结果是否为空,以返回一个空的Optional
它接受一个ListOptional,因此涵盖了比仅两个元素更一般的情况。
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);
}

0

一行代码,没有使用流,但不是特别优雅:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接