Как объединить списки в один список

У меня есть список значений, некоторые из которых могут быть списками/коллекциями или отдельными значениями. В нотации JavaScript это может выглядеть так:

const input = [1,2,[3,4], [5,6], 7];

и я хочу получить:

const concatenated = [1,2,3,4,5,6,7];

Итак, у меня есть этот код Java:

      ArrayList<T> concatenated = new ArrayList<>();

      for (T v : input) {
        try{
          concatenated.addAll((Collection) v);
        }
        catch (Exception e1){
          try{
            concatenated.addAll((List) v);
          }
          catch (Exception e2){
            concatenated.add(v);
          }
        }

     }

но этот код кажется мне довольно ужасным. Во-первых, я не знаю, достаточно ли попытки приведения к списку или коллекции - есть ли другие типы, на которые я должен попытаться привести? Есть ли ошибки, которые я не должен игнорировать?

Как это сделать правильно?

Ответы

Ответ 1

Код не нуждается в обработке Exception как таковой, если в списках нет null значений. Впрочем, в вашем случае достаточно просто привести основание instanceOf как:

// Edit: Since the type of the input 'Collection' is not bound strictly
List<Object> flatten(Collection<?> input) {
    List<Object> concatenated = new ArrayList<>();
    for (Object v : input) {
        if (v instanceof Collection) {
            concatenated.addAll(flatten((Collection<?>) v));
        } else {
            concatenated.add(v);
        }
    }
    return concatenated;
} 

использование его в дальнейшем на jshell дает мне такой вывод:

jshell> List<Object> list = List.of(1,2,List.of(3,4),List.of(5,6),7) 
list ==> [1, 2, [3, 4], [5, 6], 7]

jshell> flatten(list)
$3 ==> [1, 2, 3, 4, 5, 6, 7]

:

Ответ 2

Как уже упоминалось, использование исключений для потока управления не является идеальным. Вместо этого вы можете использовать оператор instanceof чтобы проверить, является ли элемент Collection. Ответ от nullpointer показывает хороший пример этого. Если вы хотите более общий вариант, вы можете сделать что-то вроде:

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public static <E> List<E> deepFlatten(final Iterable<?> iterable, final Class<E> type) {
    if (type.isPrimitive() || type.isArray() || Iterable.class.isAssignableFrom(type)) {
        throw new IllegalArgumentException(
                "type must not denote a primitive, array, or java.lang.Iterable type: " + type);
    }
    final List<E> result = new ArrayList<>();
    for (final Object element : iterable) {

        if (element instanceof Iterable<?>) {
            result.addAll(deepFlatten((Iterable<?>) element, type)); // recursion

        } else if (element != null && element.getClass().isArray()) {

            if (element instanceof Object[]) {
                result.addAll(deepFlatten(Arrays.asList((Object[]) element), type)); // recursion
            } else { // primitive array
                final Iterable<?> itrArray = IntStream.range(0, Array.getLength(element))
                        .mapToObj(index -> Array.get(element, index))::iterator; // method reference
                result.addAll(deepFlatten(itrArray, type)); // recursion
            }

        } else {
            /*
             * Will throw ClassCastException if any element is not an instance
             * of "type". You could also throw a NullPointerException here if
             * you don't want to allow null elements.
             */
            result.add(type.cast(element));
        }

    }
    return result;
}

Это также обрабатывает "встроенные" массивы, как и Iterable s, посредством рекурсии. Обратите внимание, что он не обрабатывает Map из-за неоднозначности; мы должны сгладить ключи или значения - или оба?

Вызов выше с:

Iterable<?> iterable = List.of(
        "A", "B", "C", "D",
        List.of("E", "F", List.of("G", "H"), "I", "J"),
        "K",
        new String[]{"L", "M", "N", "O", "P"},
        new String[][]{{"Q", "R"}, {"S", "T"}, {"U"}, {"V"}},
        new Object[]{"W", "X"},
        "Y", "Z"
);
List<String> flattened = deepFlatten(iterable, String.class);
System.out.println(flattened);

Дал мне:

[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]

Обратите внимание, что буквы расположены по порядку, потому что List и массивы имеют гарантированные порядки итераций. Если ваш Iterable содержал Set результат deepFlatten может не каждый раз находиться в одном и том же порядке.

Ответ 3

Использование Exceptions для управления потоком приложений/бизнес-логикой является анти-паттерном. Вы можете прочитать больше об этом здесь, здесь и здесь.

Относительно хранения различных типов элементов в коллекциях может быть сложно отлаживать и поддерживать. Вы можете написать свою собственную обертку и инкапсулировать обработку из использования. Вы можете сослаться на это для вдохновения.