How to concatenate lists into one list
Use of Exceptions
to control application flow/business logic is an anti-pattern. You can read more about it here, here and here.
Regarding storing different types of elements in Collections could be difficult to debug and maintain. You can write your own wrapper and encapsulate the handling of it from usage. You can refer this for an inspiration.
As others have mentioned, using exceptions for control flow is not ideal. You can instead use the instanceof
operator to test if an element is a Collection
. The answer by nullpointer shows a good example of this. If you want an more generic option you could also do something like:
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;
}
This also handles "embedded" arrays, as well as Iterable
s, through recursion. Note it doesn't handle Map
s because of the ambiguity; should we flatten the keys or the values—or both?
Calling the above with:
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);
Gave me:
[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]
Note that the letters are in order because List
s and arrays have guaranteed iteration orders. If your Iterable
contained a Set
the result of the deepFlatten
may not be in the same order each time.
The code doesn't need Exception
handling as such unless there are null
values in the lists. It should be sufficient though in your case to just cast basis of instanceOf
as:
// 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;
}
using it further on jshell gives me this output:
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]
: