我有一串字符串:
Stream<String> stream = ...;
我想构造一个字符串,以,
作为分隔符将这些项目连接起来。我这样做如下:
stream.collect(Collectors.joining(","));
现在,仅当存在多个项目时,我才想在此输出中添加前缀[
和后缀]
。例如:
a
[a,b]
[a,b,c]
是否可以先将Stream<String>
体现为List<String>
然后再检查List.size() == 1
来完成?在代码中:
public String format(Stream<String> stream) {
List<String> list = stream.collect(Collectors.toList());
if (list.size() == 1) {
return list.get(0);
}
return "[" + list.stream().collect(Collectors.joining(",")) + "]";
}
首先将流转换为列表,然后再转换为流以能够应用Collectors.joining(",")
感觉很奇怪。我认为遍历整个流(在Collectors.toList()
中完成)只是发现是否存在一个或多个项目是次优的。
我可以实现自己的Collector<String, String>
来计算给定项目的数量,然后使用该数量。但是我想知道是否有更直接的方法。
该问题有意忽略了流为空时的情况。
答案 0 :(得分:4)
是的,这可以通过使用自定义Collector
实例来实现,该实例将使用匿名对象(该对象具有流中的项目数)和重载的toString()
方法:
public String format(Stream<String> stream) {
return stream.collect(
() -> new Object() {
StringJoiner stringJoiner = new StringJoiner(",");
int count;
@Override
public String toString() {
return count == 1 ? stringJoiner.toString() : "[" + stringJoiner + "]";
}
},
(container, currentString) -> {
container.stringJoiner.add(currentString);
container.count++;
},
(accumulatingContainer, currentContainer) -> {
accumulatingContainer.stringJoiner.merge(currentContainer.stringJoiner);
accumulatingContainer.count += currentContainer.count;
}
).toString();
}
Collector
界面具有以下方法:
public interface Collector<T,A,R> {
Supplier<A> supplier();
BiConsumer<A,T> accumulator();
BinaryOperator<A> combiner();
Function<A,R> finisher();
Set<Characteristics> characteristics();
}
我将省略最后一个方法,因为它与本示例无关。
有一个带有以下签名的collect()
方法:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
,在我们的示例中,它将解决:
<Object> Object collect(Supplier<Object> supplier,
BiConsumer<Object, ? super String> accumulator,
BiConsumer<Object, Object> combiner);
supplier
中,我们使用StringJoiner
的实例(与Collectors.joining()
所使用的基本上相同)。 accumulator
中,我们使用的是StringJoiner::add()
,但我们也会增加计数combiner
中,我们使用StringJoiner::merge()
并将计数添加到累加器中format()
函数返回之前,我们需要调用toString()
方法将累积的StringJoiner
实例包装在[]
中(或保留原样,以防单元素流还可以添加一个空盒子的盒子,为了不使此收集器变得更加复杂,我将其省略。
答案 1 :(得分:0)
已经有一个可以接受的答案,我也赞成。
我仍然想提供另一种解决方案。可能是因为它有一个要求:
stream.spliterator()
中的Stream<String> stream
必须为Spliterator.SIZED
。
如果这适合您的情况,您还可以使用以下解决方案:
public String format(Stream<String> stream) {
Spliterator<String> spliterator = stream.spliterator();
StringJoiner sj = spliterator.getExactSizeIfKnown() == 1 ?
new StringJoiner("") :
new StringJoiner(",", "[", "]");
spliterator.forEachRemaining(sj::add);
return sj.toString();
}
根据JavaDoc Spliterator.getExactSizeIfKnown()
“如果此estimateSize()
是Spliterator
,则返回SIZED
,否则返回-1
。”如果Spliterator
为SIZED
,则遍历或拆分之前的“ estimateSize()
”表示有限的大小,在没有结构源修改的情况下,该大小表示要计算的元素数量的精确计数被完整遍历所遇到。”
由于“大多数用于Collection
的所有元素的集合拆分器均报告了此特征”(SIZED
的JavaDoc中的API注释),因此这可能是理想的直接方式 >。
编辑:
如果Stream
为空,我们可以立即返回一个空的String
。如果Stream
只有一个String
,则无需创建StringJoiner
并将其复制到String
中。我们直接返回单个String
。
public String format(Stream<String> stream) {
Spliterator<String> spliterator = stream.spliterator();
if (spliterator.getExactSizeIfKnown() == 0) {
return "";
}
if (spliterator.getExactSizeIfKnown() == 1) {
AtomicReference<String> result = new AtomicReference<String>();
spliterator.tryAdvance(result::set);
return result.get();
}
StringJoiner result = new StringJoiner(",", "[", "]");
spliterator.forEachRemaining(result::add);
return result.toString();
}