首页 > 解决方案 > 将列表拆分为 3 个子列表 Java 8+

问题描述

我有一个对象列表。我想做一个 groupBy 以便有:

和不是上述任何一种的一组。

我可以:

for (Object obj: myList) {
    if (obj instanceof Long) || (obj instanceof String)  {
        // add to sublist1
    } else if (obj instanceof Map) {
        // sublist2
    } else {
        // sublist3
    }
}

我如何使用 Java 8?

标签: javalambdajava-8java-stream

解决方案


当我遇到这样的问题时,我通常会“由内而外”地处理它,也就是说,我会想到一些看起来很有用的逻辑块。然后,我通过组合构建完整的问题。

例如,您需要一种方法来根据对象是否是一组类中的一个的实例来区分对象。(对于 Integer、Long、String 和 Boolean 的集合,我假设您的意思是类似于原始的类,而不是数字。)这向我建议了一个函数,它接受一个对象和一组这样的类并确定该对象是否是其中任何一个的实例。这就是它的样子:

boolean instanceOfAny(Object obj, Set<Class<?>> set) {
    return set.stream().anyMatch(clazz -> clazz.isInstance(obj));
}

并非巧合,这个函数的形状像一个Predicate<Object>. 你可以这样称呼它:

if (instanceOfAny(obj, Set.of(Integer.class, Long.class)) { ...

问题是,我们有几个这样的谓词,我们要根据谓词匹配的东西对事物进行分组。这建议了一个谓词列表。您希望在一组中使用类似原始的类,在第二组中使用 Maps。该列表如下所示:

List<Predicate<Object>> predicates = List.of(
    obj -> instanceOfAny(obj, Set.of(Integer.class, Long.class, String.class, Boolean.class)),
    obj -> instanceOfAny(obj, Set.of(Map.class)));

有一个关于如何处理“其他”案件的问题。您可以在最后放置一个谓词obj -> true,这将保证匹配。但是匹配这些谓词的代码仍然会出现没有谓词匹配的情况。您可以在那里抛出一个断言错误,或者只是将如果没有谓词匹配,则将该项目放入另一个组的代码中烘焙。

由于我们有一个谓词列表,因此对它们进行分组的自然值是列表中谓词的索引。我们可以稍微随意地为“不匹配”组分配一个值。由于它在问题陈述中最后列出,因此我将在列表中的最后一个索引之外分配一个“不匹配”索引。

给定一个对象,我们可以在列表上编写一个循环,并依次调用对象上的每个谓词。但是我们想使用流和 lambda,所以让我们这样做。(我实际上认为无论如何使用流效果都很好。)下面是如何做到这一点,使用旧的 IntStream-over-list-indices 技巧:

int grouper(Object obj) {
    return IntStream.range(0, predicates.size())
                    .filter(i -> predicates.get(i).test(obj))
                    .findFirst()
                    .orElse(predicates.size());
}

在这里,我们流过列表索引并从filter操作中调用每个谓词。这为我们提供了匹配的谓词的索引。我们只想要第一个,所以我们使用findFirst. 这给了我们一个OptionalInt,如果没有任何谓词匹配,则它是空的,因此我们在这种情况下替换列表大小。

现在,让我们给它一些输入:

List<Object> input = List.of(
    true, 1, 2L, "asdf", Map.of("a", "b"), new BigInteger("23456"),
    Map.of(3, 4), List.of("x", "y", "z"), false, 17, 'q');

为了处理输入,我们使用这个短流:

Map<Integer, List<Object>> result = input.stream().collect(groupingBy(this::grouper));
result.forEach((k, v) -> System.out.println(k + " => " + v));

输出是:

0 => [true, 1, 2, asdf, false, 17]
1 => [{a=b}, {3=4}]
2 => [23456, [x, y, z], q]

综上所述,我们有以下几点:

boolean instanceOfAny(Object obj, Set<Class<?>> set) {
    return set.stream().anyMatch(clazz -> clazz.isInstance(obj));
}

List<Predicate<Object>> predicates = List.of(
    obj -> instanceOfAny(obj, Set.of(Integer.class, Long.class, String.class, Boolean.class)),
    obj -> instanceOfAny(obj, Set.of(Map.class)));

int grouper(Object obj) {
    return IntStream.range(0, predicates.size())
                    .filter(i -> predicates.get(i).test(obj))
                    .findFirst()
                    .orElse(predicates.size());
}

void main() {
    List<Object> input = List.of(
        true, 1, 2L, "asdf", Map.of("a", "b"), new BigInteger("23456"),
        Map.of(3, 4), List.of("x", "y", "z"), false, 17, 'q');

    Map<Integer, List<Object>> result =
        input.stream().collect(groupingBy(this::grouper));

    result.forEach((k, v) -> System.out.println(k + " => " + v));
}

推荐阅读