本文介绍了仅当存在多个项目时,才向Collectors.joining()添加前缀和后缀的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一串字符串:

  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() [] 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 of StringJoiner (basically the same thing that Collectors.joining() is using).
  • In the accumulator, we are using StringJoiner::add() but we increment the count as well
  • In the combiner, we are using StringJoiner::merge() and add the count to the accumulator
  • Before returning from format() function, we need to call toString() method to wrap our accumulated StringJoiner 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()添加前缀和后缀的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-30 02:32