Java 8 streams: conditional Collector

丶灬走出姿态 提交于 2019-12-05 16:23:30

You could do it via:

 return values.stream()
            .map(x -> "'" + x + "'")
            .collect(Collectors.collectingAndThen(Collectors.toList(),
                    list -> {
                        if (list.size() == 1) {
                            return "value" + list.get(0);
                        }
                        if (list.size() > 1) {
                            return String.join(",", list);
                        }
                        return "Nothing found";
                    }));

Unfortunately, the StringJoiner used by the joining() collector only allows an alternative representation for the “no values” case, but not for the single value case. To add that feature, we have to track the count manually, e.g.

public static String log(List<String> values) {
    return values.stream()
                 //.filter(...)
                 .collect(
                         () -> new Object() {
                             StringJoiner sj = new StringJoiner("', '", "'", "' added");
                             int num;
                             String result() {
                                 return num==0? "No values added":
                                                (num==1? "Value ": "Values ")+sj;
                             }
                         },
                         (o,s) -> { o.sj.add(s); o.num++; },
                         (o,p) -> { o.sj.merge(p.sj); o.num+=p.num; }
                 ).result();
}

This is quiet complicated, but a “clean” solution; it would even work with parallel streams, if ever needed.

Example

System.out.println(log(Arrays.asList("A", "B", "C")));
System.out.println(log(Arrays.asList("A")));
System.out.println(log(Collections.emptyList()));
Values 'A', 'B', 'C' added
Value 'A' added
No values added

One core idea of streams is to have a look at the contained elements individually, possibly in parallel. There are aggregate operations (like count) that consider all (remaining) elements of a stream. The collect method also is an aggregate, in the sense that it consumes all elements. However, only after it is finished the exact number of items is known.

In your case I would collect the middle part of the string (comma separated list of elements) and add the prefix "Value" or "Values" afterwards.

I suggest you first find the elements you wish to join, and then join them (after you find their count):

public static String log(List<String> values) {
    List<String>
        elements = values.stream()
                       //.filter(...)
                         .map(x -> "'" + x + "'")
                         .collect(Collectors.toList());
    String joined = String.join (",", elements);
    return (elements.size () == 1 ? "value " : "values:") + joined + " added";
}

It doesn't sound like a good idea to count the elements via some side effect of one of the intermediate Stream methods.

And another interesting option probably would be this:

public static String log(List<String> values) {
    Spliterator<String> sp = values.stream()
            .map(x -> "'" + x + "'")
            .spliterator();

    StringBuilder sb = new StringBuilder("Value = ");

    sp.tryAdvance(x -> sb.append(x));
    if (sp.tryAdvance(x -> {
        sb.replace(5, 6, "s ");
        sb.append(",").append(x);
    })) {
        sp.forEachRemaining(x -> {
            sb.append(",").append(x);
        });
    }

    return sb.toString();

}

The advantage is that you don't need to collect to a List to further append each of them separately.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!