如何将嵌套 Java 集合中的所有项目平展到单个列表中?

2022-09-03 03:50:21

给定一个复杂的嵌套对象集合,例如:

Set<List<Map<String, List<Object>>>> complexNestedCollection;

是否存在一种通用方法来将其扁平化并获取其中包含的所有s中的单个?ListObject

一些细节:

  1. 该列表不应包含集合对象本身或映射键 - 仅包含最低级别的值。
  2. 它应该尽可能遵循相同的顺序 - 因此在示例中,列表中的项目将是有序的,而映射/集合的顺序将取决于实现。
  3. 它可以选择排除重复项
  4. 更新:理想情况下,它应该检测/处理任何级别的循环引用,例如,外部列表将自身包含为成员。(感谢Adrian Jałoszewski在下面的评论中提到了这一点)。List<List<Object>>

注意:实际的用例是从List<List<String>>中获取所有字符串,这可以通过两个循环轻松完成,但它让我想知道一般情况。


答案 1

假设您使用Java 8,您可以使用Stream API做到这一点,这要归功于flatMap(Function<? super T,? extends Stream<? extends R>> mapper)的方法,如下所示:

// 1. Convert the Set as a Stream of List<Map<String, List<Object>>>
// 2. Extract the elements of the lists to get a Stream of Map<String, List<Object>>
// 3. Extract values of the maps to get a Stream of List<Object>
// 4. Extract the elements of the lists to get a Stream of Object
// 5. Get rid of duplicates
// 6. Collect the result as a List of Object
List<Object> result = complexNestedCollection.stream()
    .flatMap(List::stream)
    .flatMap(m -> m.values().stream())
    .flatMap(List::stream)
    .distinct()
    .collect(Collectors.toList());

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

返回一个流,该流由将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果组成。每个映射流在其内容放入此流后都将关闭。(如果映射的流为 null,则使用空流。


对于 以前版本的 ,您仍然可以使用 Google GuavaFluentIterable 来替换和使用 transformAndConcat(Function<? super E,? extend Iterable<? extend T>> 函数),而不是扁平化您的集合。JavaStreamflatMap

然后,前面的代码片段将按如下方式重写:

List<Object> result =
    new ArrayList<>(
        new LinkedHashSet<>(
            FluentIterable.from(complexNestedCollection)
                .transformAndConcat(
                    new Function<List<Map<String, List<Object>>>, Iterable<Map<String, List<Object>>>> () {
                        public Iterable<Map<String, List<Object>>> apply(final List<Map<String, List<Object>>> input) {
                            return input;
                        }
                    }
                ).transformAndConcat(
                    new Function<Map<String, List<Object>>, Iterable<List<Object>>> () {
                        public Iterable<List<Object>> apply(final Map<String, List<Object>> input) {
                            return input.values();
                        }
                    }
                ).transformAndConcat(
                    new Function<List<Object>, Iterable<Object>> () {
                        public Iterable<Object> apply(final List<Object> input) {
                            return input;
                        }
                    }
                ).toList()
        )
    );

答案 2

我不确定这个确切的实现是否有效,因为它充满了未经检查的警告和其他危险的东西,但你应该大致了解一下。

public static Set<Object> recursiveExtract(Object stuff) {

    Set<Object> set = new HashSet<Object>();

    if(stuff instanceof Iterable) {
        for(Object o : (Iterable<?>)stuff) {
            set.addAll(recursiveExtract(o));
        }
    } else if(stuff instanceof Map) {
        for(Object o : ((Map<?, ? extends Object>) stuff).values()) {
            set.addAll(recursiveExtract(o));
        }
    } else {
        set.add(stuff);
    }

    return set;
}

如果您坚持使用List,也可以使用,但这样您可能会得到重复的结果,或者如果您关心顺序。List<Object>LinkedHashSet<Object>


请不要投反对票,给我改进的建议。它更好。


推荐