将列表拆分为 3 个子列表 Java 8+

Mor*_*ive 5 java lambda java-8 java-stream

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

  • 一组数字类型,例如整数、长整数、字符串或布尔值
  • 一组地图

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

我可以:

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

我如何使用 Java 8?

Stu*_*rks 8

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

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

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

并非巧合,此函数的形状类似于Predicate<Object>. 你可以这样称呼它:

if (instanceOfAny(obj, Set.of(Integer.class, Long.class)) { ...
Run Code Online (Sandbox Code Playgroud)

问题是,我们有几个这样的谓词,我们将要根据谓词匹配的内容对事物进行分组。这建议了一个谓词列表。您希望一组中的类原始类和第二组中的 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)));
Run Code Online (Sandbox Code Playgroud)

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

由于我们有一个谓词列表,将它们分组的自然值是列表中谓词的索引。我们可以随意为“不匹配”组分配一个值。由于它在问题陈述的最后列出,我将分配一个超出列表中最后一个索引的“不匹配”索引。

给定一个对象,我们可以在列表上编写一个循环并依次调用该对象上的每个谓词。但是我们想使用流和 lambdas,所以让我们这样做。(我实际上认为无论如何使用流效果很好。)这是使用旧的 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());
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们遍历列表索引并从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');
Run Code Online (Sandbox Code Playgroud)

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

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

输出是:

0 => [true, 1, 2, asdf, false, 17]
1 => [{a=b}, {3=4}]
2 => [23456, [x, y, z], q]
Run Code Online (Sandbox Code Playgroud)

综合起来,我们有以下几点:

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));
}
Run Code Online (Sandbox Code Playgroud)


Eug*_*ene 1

像这样的东西吗?

public static void main(String[] args) {
    List<Object> test = new ArrayList<>();
    test.add(2L);
    test.add("me");
    test.add(new Object());

    Set<Class<?>> left = Set.of(Integer.class, Long.class, String.class, Boolean.class);

    Map<Boolean, List<Object>> map =
        test.stream()
            .collect(Collectors.partitioningBy(
                x -> left.contains(x.getClass())
            ));

    // add these to subList1
    map.get(Boolean.TRUE).forEach(System.out::println);
    System.out.println("=====");
    map.get(Boolean.FALSE).forEach(System.out::println);
}
Run Code Online (Sandbox Code Playgroud)

编辑

不幸的是,对于更多类型,事情会变得复杂。主要是因为,正如您所说,您有一张instanceOf支票Map;但我上面向您展示的方法行不通。因此,您可以建立广泛的搜索,例如:

enum Type {
    ONE,
    TWO,
    THREE
}

static final Map<Class<?>, Type> MAP = Map.of(
    Long.class, Type.ONE,
    Integer.class, Type.ONE,
    String.class, Type.ONE,
    Boolean.class, Type.ONE,
    Map.class, Type.TWO
);
Run Code Online (Sandbox Code Playgroud)

并以以下形式使用它:

public static void main(String[] args) {
    List<Object> test = new ArrayList<>();
    test.add(2L);
    test.add("me");
    test.add(new HashMap<>());
    test.add(new Object());

    Map<Type, List<Object>> map =
        test.stream()
            .collect(Collectors.groupingBy(
                x -> {
                    Type t = MAP.get(x.getClass());
                    if (t == null) {
                        for (Entry<Class<?>, Type> entry : MAP.entrySet()) {
                            if (entry.getKey().isAssignableFrom(x.getClass())) {
                                return entry.getValue();
                            }
                        }
                        return Type.THREE;
                    } else {
                        return t;
                    }
                }
            ));

    map.get(Type.ONE).forEach(System.out::println);
    System.out.println("=====");
    map.get(Type.TWO).forEach(System.out::println);
    System.out.println("=====");
    map.get(Type.THREE).forEach(System.out::println);
}
Run Code Online (Sandbox Code Playgroud)

  • @MoreThanFive 另一个“Set”和“Collectors.groupingBy”? (2认同)