首页 > 解决方案 > Java Streams - 根据列表的值组成一个值

问题描述

使用 Java 8 并使用 Streams 处理某些对象的列表,我需要根据其他值的属性为列表的每个值分配一个属性。

想象一下具有以下属性的 Java MyClass 中的类:

String value1;
String value2;
LocalDate theDate;
String uniqueId;

想象一下 MyClass 对象的列表,就像:

List<MyClass> myList = new ArrayList<>();

uniqueId在本练习开始时,该属性是空的。

映射列表后,uniqueId必须按照以下策略设置:

<value1>-<value2>-N

其中N<value1>-<value2>按 排序的组合出现的次数theDate

鉴于 JSON 中给出的这个示例:

[
{"value1":"1111111", "value2":"A", "theDate": "2020-06-01", "uniqueId": null},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-02", "uniqueId": null},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-03", "uniqueId": null},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-04", "uniqueId": null},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-01", "uniqueId": null},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-02", "uniqueId": null},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-03", "uniqueId": null}
]

执行后应导致:

[
{"value1":"1111111", "value2":"A", "theDate": "2020-06-01", "uniqueId": "1111111-A-1"},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-02", "uniqueId": "1111111-A-2"},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-03", "uniqueId": "1111111-A-3"},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-04", "uniqueId": "1111111-A-4"},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-01", "uniqueId": "1111111-B-1"},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-02", "uniqueId": "1111111-B-2"},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-03", "uniqueId": "1111111-B-3"}
]

我显然可以使用传统的循环策略来做到这一点。但问题是,使用 Streams,我如何设法跟踪列表的其余值,以便我可以组成我的组合值?还是不能使用 Streams?

myList.stream()
    .map(o -> {
    // How do I make here to keep the track of the rest of the values of the list?
    })
    .collect(Collectors.toList());

谢谢你。

标签: javajava-stream

解决方案


这不是真正的东西是流闪耀,这有两个原因。首先是您正在MyClass通过副作用操作对象(当然,这是可以避免的),并且因为 Java 缺乏对mapWithIndexor之类的开箱即用的支持zipWithIndex,这在这里非常有帮助。您可以在此处找到有关如何解决该特定问题的更多信息。

无论如何,可以通过流进行。

首先,我定义了这个辅助方法:

    public static MyClass setUniqueId(MyClass myClass, int i) {
        myClass.uniqueId = String.format("%s-%s-%s", myClass.value1, myClass.value2, i);
        return myClass;
    }

它应该是相当不言自明的 - 它使用提供的 int 设置唯一 id 并返回对象本身,现在使用 id 集。

value1然后,通过按相等和value2值对流中的元素进行分组,可以做你想做的事。对元素进行分组后,我们可以通过使用它们的索引进行压缩来设置它们的唯一 ID。由于 Java 缺乏对此的支持,我们需要通过使用IntStream带有我们用来从中获取索引的范围的范围来以一种倒退的方式来做到这一点。把它们放在一起,我们得到这个:

        list.stream()
            .collect(Collectors.groupingBy(obj -> obj.value1 + "-" + obj.value2))
            .forEach((klass, values) -> {
                IntStream.range(0, values.size())
                    .forEach(i -> setUniqueId(values.get(i), i));
        });

另一种可能更清晰的方法是对分组结果进行 flatMap:

        var results = list.stream()
            .collect(Collectors.groupingBy(obj -> obj.value1 + "-" + obj.value2))
            .values().stream()
            .flatMap(values -> 
                IntStream.range(0, values.size())
                    .mapToObj(i -> setUniqueId(values.get(i), i)))
            .sorted((a, b) -> a.date.compareTo(b.date))
            .collect(Collectors.toList());

推荐阅读