Java 8 个流:条件收集器
Java 8 streams: conditional Collector
我想使用 Java 8 个流将字符串值列表转换为单个字符串。像 "A"、"B" 这样的值列表应该 return 像 "Values: 'A', 'B' added" 这样的字符串。这工作正常,但我想根据值的数量更改 Pre- 和 Postfix。例如,如果我有一个只有 "A" 的列表,我希望结果字符串为 "Value 'A' added".
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
public class HelloWorld
{
public static void main(String[] args)
{
List<String> values = new ArrayList<>();
values.add("A");
values.add("B");
values.add("C");
List<String> value = new ArrayList<>();
value.add("A");
System.out.println(log(values));
System.out.println(log(value));
}
public static String log(List<String> values){
return values.stream()
//.filter(...)
.map(x -> "'" + x + "'")
.collect(Collectors.joining(",","values:"," added"));
}
}
有没有办法根据结果列表的大小更改 Collctor?然后我可以做类似
的事情
.collect(Collectors.joining(",", size = 1 ? "Value " : "Values: "," added"));
我更喜欢没有中间列表结果的单流操作。我也事先不知道结果,因为我过滤了流。
编辑:我最终采纳了 Eugene 的建议。我想做的是找到两个列表之间的差异和 return 人类可读形式的差异。效果很好!
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
public class HelloWorld
{
public static void main(String[] args)
{
List<String> oldValues = new ArrayList<>();
oldValues.add("A");
oldValues.add("B");
oldValues.add("C");
List<String> newValues = new ArrayList<>();
newValues.add("A");
newValues.add("C");
newValues.add("D");
newValues.add("E");
System.out.println(HelloWorld.<String>log(oldValues, newValues, " deleted"));
System.out.println(HelloWorld.<String>log(newValues, oldValues, " added"));
}
public static <T> String log(List<T> first, List<T> 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<String> 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
流的一个核心思想是单独查看包含的元素,可能是并行的。有考虑流的所有(剩余)元素的聚合操作(如 count
)。 collect
方法也是一个聚合,因为它会消耗所有元素。但是,只有在完成后才能知道确切的项目数。
在你的情况下,我会收集字符串的中间部分(逗号分隔的元素列表)并在之后添加前缀 "Value"
或 "Values"
。
我建议你先找到你想加入的元素,然后加入它们(在你找到它们的数量之后):
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";
}
通过中间 Stream
方法之一的一些副作用来计算元素听起来不是一个好主意。
您可以通过以下方式完成:
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";
}));
另一个有趣的选项可能是:
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();
}
优点是您不需要收集到 List
以进一步单独附加它们中的每一个。
不幸的是,joining()
收集器使用的 StringJoiner
只允许对“无值”情况进行替代表示,但不允许对单值情况进行表示。要添加该功能,我们必须手动跟踪计数,例如
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();
}
这很复杂,但却是一个“干净”的解决方案;如果需要,它甚至可以与并行流一起工作。
例子
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
我想使用 Java 8 个流将字符串值列表转换为单个字符串。像 "A"、"B" 这样的值列表应该 return 像 "Values: 'A', 'B' added" 这样的字符串。这工作正常,但我想根据值的数量更改 Pre- 和 Postfix。例如,如果我有一个只有 "A" 的列表,我希望结果字符串为 "Value 'A' added".
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
public class HelloWorld
{
public static void main(String[] args)
{
List<String> values = new ArrayList<>();
values.add("A");
values.add("B");
values.add("C");
List<String> value = new ArrayList<>();
value.add("A");
System.out.println(log(values));
System.out.println(log(value));
}
public static String log(List<String> values){
return values.stream()
//.filter(...)
.map(x -> "'" + x + "'")
.collect(Collectors.joining(",","values:"," added"));
}
}
有没有办法根据结果列表的大小更改 Collctor?然后我可以做类似
的事情.collect(Collectors.joining(",", size = 1 ? "Value " : "Values: "," added"));
我更喜欢没有中间列表结果的单流操作。我也事先不知道结果,因为我过滤了流。
编辑:我最终采纳了 Eugene 的建议。我想做的是找到两个列表之间的差异和 return 人类可读形式的差异。效果很好!
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
public class HelloWorld
{
public static void main(String[] args)
{
List<String> oldValues = new ArrayList<>();
oldValues.add("A");
oldValues.add("B");
oldValues.add("C");
List<String> newValues = new ArrayList<>();
newValues.add("A");
newValues.add("C");
newValues.add("D");
newValues.add("E");
System.out.println(HelloWorld.<String>log(oldValues, newValues, " deleted"));
System.out.println(HelloWorld.<String>log(newValues, oldValues, " added"));
}
public static <T> String log(List<T> first, List<T> 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<String> 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
流的一个核心思想是单独查看包含的元素,可能是并行的。有考虑流的所有(剩余)元素的聚合操作(如 count
)。 collect
方法也是一个聚合,因为它会消耗所有元素。但是,只有在完成后才能知道确切的项目数。
在你的情况下,我会收集字符串的中间部分(逗号分隔的元素列表)并在之后添加前缀 "Value"
或 "Values"
。
我建议你先找到你想加入的元素,然后加入它们(在你找到它们的数量之后):
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";
}
通过中间 Stream
方法之一的一些副作用来计算元素听起来不是一个好主意。
您可以通过以下方式完成:
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";
}));
另一个有趣的选项可能是:
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();
}
优点是您不需要收集到 List
以进一步单独附加它们中的每一个。
不幸的是,joining()
收集器使用的 StringJoiner
只允许对“无值”情况进行替代表示,但不允许对单值情况进行表示。要添加该功能,我们必须手动跟踪计数,例如
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();
}
这很复杂,但却是一个“干净”的解决方案;如果需要,它甚至可以与并行流一起工作。
例子
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