Java 8流:条件收集器

问题描述 投票:4回答:5

我想使用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<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"));

  }
}

有没有办法更改收集器,具体取决于结果列表的大小?然后我可以做类似的事情

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

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

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

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
java java-stream collectors
5个回答
2
投票

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

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

4
投票

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

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

2
投票

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

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


2
投票

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

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方法的一些副作用计算元素听起来不是一个好主意。


2
投票

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

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来进一步单独附加它们。

© www.soinside.com 2019 - 2024. All rights reserved.