Java 8流:条件收集器

我想使用Java 8流将List of String值转换为单个String。 像“A”,“B”这样的值列表应返回一个字符串,如“值:’A’,’B’已添加”。 这工作正常,但我想根据值的数量更改前缀和后缀。 例如,如果我有一个只有“A”的列表,我希望生成的字符串为“值’A’添加”。

import java.util.stream.Collectors; import java.util.ArrayList; import java.util.List; public class HelloWorld { public static void main(String[] args) { List values = new ArrayList(); values.add("A"); values.add("B"); values.add("C"); List value = new ArrayList(); value.add("A"); System.out.println(log(values)); System.out.println(log(value)); } public static String log(List values){ return values.stream() //.filter(...) .map(x -> "'" + x + "'") .collect(Collectors.joining(",","values:"," added")); } } 

有没有办法更改Collctor,具体取决于生成的List的大小? 然后我可以做类似的事情

 .collect(Collectors.joining(",", size = 1 ? "Value " : "Values: "," added")); 

我更喜欢没有中间List结果的单个流操作。 我也事先不知道结果,因为我过滤了流。

编辑:我最终使用了尤金的建议。 我想要做的是找到两个列表之间的差异,并返回人类可读forms的差异。 很好用!

 import java.util.stream.Collectors; import java.util.ArrayList; import java.util.List; public class HelloWorld { public static void main(String[] args) { List oldValues = new ArrayList(); oldValues.add("A"); oldValues.add("B"); oldValues.add("C"); List newValues = new ArrayList(); newValues.add("A"); newValues.add("C"); newValues.add("D"); newValues.add("E"); System.out.println(HelloWorld.log(oldValues, newValues, " deleted")); System.out.println(HelloWorld.log(newValues, oldValues, " added")); } public static  String log(List first, List second, String postfix) { return (String) first .stream() .filter(x -> !second.contains(x)) .map(x -> "'" + x.toString() + "'"). collect(Collectors.collectingAndThen(Collectors.toList(), list -> { if (list.size() == 1) { return "Value " + list.get(0) + postfix; } if (list.size() > 1) { List strings = new ArrayList(list.size()); for (Object object : list) { strings.add(object.toString()); } return "Values: " + String.join(",", strings) + postfix; } return ""; })); } } 

输出:

 Value 'B' deleted Values: 'D','E' added 

你可以通过以下方式完成:

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

不幸的是, StringJoiner joining()收集器使用的StringJoiner只允许“无值”情况的替代表示,但不允许单值情况。 要添加该function,我们必须手动跟踪计数,例如

 public static String log(List 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(); } 

这是一个安静的复杂,但一个“干净”的解决方案; 如果需要,它甚至可以用于并行流。

 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 

流的一个核心思想是单独查看包含的元素,可能并行查看。 有一些聚合操作(如count )会考虑流的所有(剩余)元素。 collect方法也是一个聚合,因为它消耗所有元素。 但是,只有在完成后才知道确切的项目数。

在你的情况下,我将收集字符串的中间部分(逗号分隔的元素列表),然后添加前缀"Value""Values"

我建议你先找到你想要加入的元素,然后加入它们(在你找到它们之后):

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

通过一种中间Stream方法的某些副作用来计算元素并不是一个好主意。

另一个有趣的选择可能是这样的:

 public static String log(List values) { Spliterator 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(); } 

优点是您不需要收集到List以进一步单独附加每个List