There are two main issues:
Type Exception
is a checked exception. The problem is that none of the built-in JDK functions declares to throw a checked exception, which imposes restriction on their implementations. Therefore, the simplest solution would be to remove throws clause, that would allow to invoke flatten()
recursively from the stream without any complaints.
The second problem stems from the need to coerce the elements to the Integer
type. I would advise providing Class<T>
as an argument it would make the solution both clean an versatile. Or alternatively, return type can be changed to Object[]
(chose the option which is more suitable depending on your needs).
Let's start with a simple implementation that returns array of type Object[]
. In order to implement flattening logic I've used Java 16 mapMulti()
since it's more suitable when you need to incorporate imperative logic into the stream.
public static Object[] flatten(Object[] inputArray) {
return Arrays.stream(inputArray)
.mapMulti((element, consumer) -> {
if (element instanceof Object[] arr) for (var next: flatten(arr)) consumer.accept(next);
else consumer.accept(element);
})
.toArray();
}
If we switch to generic implementation, the return type needs to be changed to a List<T>
(or Collection<T>
), otherwise we run into a problem of creating a generic array. Generics and array do not play well together and it highly advisable to favor Collections over arrays, hence I'll go with List<T>
as a return type.
public static <T> List<T> flatten(Object[] inputArray, Class<T> tClass) {
return Arrays.stream(inputArray)
.<T>mapMulti((element, consumer) -> {
if (element instanceof Object[] arr) for (var next: flatten(arr, tClass)) consumer.accept(next);
else consumer.accept(tClass.cast(element));
})
.toList();
}
main()
public static void main(String[] args) {
Object[] array = { 1, 2, new Object[]{ 3, 4, new Object[]{ 5 }, 6, 7 }, 8, 9, 10 };
System.out.println(Arrays.toString(flatten(array)));
Object[] string = { "A", "B", new Object[]{ "C", "D", new Object[]{ "E" }, "F", "G" }, "H", "I", "J" };
List<String> flattenedString = flatten(string, String.class);
System.out.println(flattenedString);
}
Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[A, B, C, D, E, F, G, H, I, J]
Update
Here's another way to solve this problem using Stream.flatMap()
, built upon this answer by @Holger.
For that, we can split the logic into two parts: a helper-method that performs recursive flattening and a method that generates the result of the required type.
And, well, since it's another solution I've decided to change the resulting type to T[]
(in case if you really need an array as a result). There's no clean and easy way to create a generic array which should be exposed to the outside context (in this case, creating Object[]
and casting to T[]
would result in ClassCastException
). To address this issue, we can employ the Reflection API to allocate the array in memory via Array.newInstance()
and then type cast it.
public static <T> T[] flatten(Object[] inputArray, Class<T> tClass) {
return flattenAsStream(inputArray, tClass)
.toArray(n -> (T[]) Array.newInstance(tClass, n));
}
public static <T> Stream<T> flattenAsStream(Object[] inputArray, Class<T> tClass) {
return Arrays.stream(inputArray)
.flatMap(e -> e instanceof Object[] arr ?
flattenAsStream(arr, tClass) : Stream.of(tClass.cast(e))
);
}