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.