问题描述
我有一串字符串:
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);
}
返回[+ list.stream()。collect(Collectors.joining(,))+];
}
首先将流转换为列表然后再转换为stream能够应用 Collectors.joining(,)
。我认为循环遍历整个流(这是在 Collectors.toList()
期间完成)只是为了发现是否存在一个或多个项目。
我可以实现自己的收集器< String,String>
,它计算给定项目的数量并使用该计数然后。但是我想知道是否有一种直接的方式。
当流为空时,这个问题故意忽略了这种情况。
是的,可以使用自定义收集器
实例,该实例将使用具有流中项目数的匿名对象和一个重载的 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 +];
}
},
(容器,currentString) - > {
container.stringJoiner.add(currentString);
con tainer.count ++;
},
(accumulatingContainer,currentContainer) - > {
accumulatingContainer.stringJoiner.merge(currentContainer.stringJoiner);
accumulatingContainer.count + = currentContainer.count;
}
).toString();
}
说明
界面具有以下方法:
public接口收集器< T,A,R> {
供应商< A>供应商();
BiConsumer< A,T>累加器();
BinaryOperator< A>组合器();
函数< A,R>装订();
设置<特征>特性();
}
我将省略最后一个方法,因为它与此示例无关。 / p>
有一个带有以下签名的 collect()
方法:
< R> R collect(供应商< R>供应商,
BiConsumer< R,?super T>累积器,
BiConsumer< R,R>组合器);
在我们的案例中,它将解析为:
<对象>对象收集(供应商<对象>供应商,
BiConsumer<对象,?超级字符串>累加器,
BiConsumer<对象,对象>组合器);
- 在
供应商
,我们正在使用StringJoiner
的实例(基本上与Collectors.joining()
正在使用的相同) 。 - 在
累加器
中,我们使用StringJoiner :: add()
但我们也增加了计数 - 在
组合器
中,我们使用StringJoiner :: merge()
并将计数添加到累加器 - 在从
format()
函数返回之前,我们需要调用toString()
在[] $ c $中包装累积的
StringJoiner
实例的方法c>(或保留原样,如果是单元素流
空案例也可以补充说,我把它留下来是为了不让这个收藏家更复杂。
I have a stream of strings:
Stream<String> stream = ...;
I want to construct a string which concatenates these items with ,
as a separator. I do this as following:
stream.collect(Collectors.joining(","));
Now I want add a prefix [
and a suffix ]
to this output only if there were multiple items. For example:
a
[a,b]
[a,b,c]
Can this be done without first materializing the Stream<String>
to a List<String>
and then checking on List.size() == 1
? In code:
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(",")) + "]";
}
It feels odd to first convert the stream to a list and then again to a stream to be able to apply the Collectors.joining(",")
. I think it's suboptimal to loop through the whole stream (which is done during a Collectors.toList()
) only to discover if there is one or more item(s) present.
I could implement my own Collector<String, String>
which counts the number of given items and use that count afterwards. But I am wondering if there is a directer way.
This question intentionally ignores there case when the stream is empty.
Yes, this is possible using a custom Collector
instance that will use an anonymous object with a count of items in the stream and an overloaded toString()
method:
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();
}
Explanation
Collector
interface has the following methods:
public interface Collector<T,A,R> {
Supplier<A> supplier();
BiConsumer<A,T> accumulator();
BinaryOperator<A> combiner();
Function<A,R> finisher();
Set<Characteristics> characteristics();
}
I will omit the last method as it is not relevant for this example.
There is a collect()
method with the following signature:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
and in our case it would resolve to:
<Object> Object collect(Supplier<Object> supplier,
BiConsumer<Object, ? super String> accumulator,
BiConsumer<Object, Object> combiner);
- In the
supplier
, we are using an instance ofStringJoiner
(basically the same thing thatCollectors.joining()
is using). - In the
accumulator
, we are usingStringJoiner::add()
but we increment the count as well - In the
combiner
, we are usingStringJoiner::merge()
and add the count to the accumulator - Before returning from
format()
function, we need to calltoString()
method to wrap our accumulatedStringJoiner
instance in[]
(or leave it as is is, in case of a single-element stream
The case for an empty case could also be added, I left it out in order not to make this collector more complicated.
这篇关于仅当存在多个项目时,才向Collectors.joining()添加前缀和后缀的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!