简体   繁体   English

Java 8流:条件收集器

[英]Java 8 streams: conditional Collector

I want to use Java 8 streams to convert a List of String values to a single String. 我想使用Java 8流将List of String值转换为单个String。 A List of values like "A", "B" should return a String like "Values: 'A', 'B' added". 像“A”,“B”这样的值列表应返回一个字符串,如“值:'A','B'已添加”。 This works fine, however I want to change the Pre- and Postfix depending on the amount of values. 这工作正常,但我想根据值的数量更改前缀和后缀。 For example, if I have a List of only "A" I want the resulting String to be "Value 'A' added". 例如,如果我有一个只有“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"));

  }
}

Is there a way to change the Collctor, depending on the size of the resulting List? 有没有办法更改Collctor,具体取决于结果列表的大小? Then I could do something like 然后我可以做类似的事情

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

I would prefer a single stream operation without an intermediate List result. 我更喜欢没有中间List结果的单个流操作。 I also do not know the result beforehand, because I filter the stream. 我也事先不知道结果,因为我过滤了流。

Edit: I ended up using Eugene's suggestion. 编辑:我最终使用了尤金的建议。 What I wanted to do is find the differences between two Lists and return the differences in human readable form. 我想要做的是找到两个列表之间的差异,并返回人类可读形式的差异。 Works nicely! 很好用!

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

Outputs: 输出:

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

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. 不幸的是, StringJoiner joining()收集器使用的StringJoiner只允许“无值”情况的替代表示,但不允许单值情况。 To add that feature, we have to track the count manually, eg 要添加该功能,我们必须手动跟踪计数,例如

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. 有一些聚合操作(如count )可以考虑流的所有(剩余)元素。 The collect method also is an aggregate, in the sense that it consumes all elements. collect方法也是一个聚合,在某种意义上它消耗所有元素。 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. 在你的情况下,我将收集字符串的中间部分(逗号分隔的元素列表),然后添加前缀"Value""Values"

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

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

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. 优点是您不需要收集到List以进一步单独附加每个List

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM