π Reading time: 3 minutes
Today I ran into an interesting problem: extracting two lists from a collection of generic Object instances, knowing that inside I'd find objects of two distinct types (A and B) that I cared about, plus a set of objects of other types to ignore.
π Solution 1
private Tuple2<List<String>, List<Car>> solution1(final List<Object> objects) {
final List<String> strings = new ArrayList<>();
final List<Car> cars = new ArrayList<>();
for (final Object object : objects) {
if (object instanceof String str) {
strings.add(str);
} else if (object instanceof Car car) {
cars.add(car);
}
}
return Tuples.of(strings, cars);
}
- iterates the list just once with a
forloop andinstanceofchecks, guaranteeing O(n) complexity and reducing the computational load - populates the lists directly during iteration, with a more direct and contained allocation
- more explicit, clearer for anyone not familiar with the Stream API, but more verbose
π Solution 2
private Tuple2<List<String>, List<Car>> solution2(final List<Object> objects) {
return Tuples.of(
filterAndCast(objects, String.class),
filterAndCast(objects, Car.class));
}
private static <T> List<T> filterAndCast(final List<?> objects, final Class<T> clazz) {
return objects.stream().filter(clazz::isInstance).map(clazz::cast).toList();
}
- uses Java's Stream API, with two passes to filter and cast, which increases the cost on large lists while still keeping O(n) complexity
- the stream might introduce temporary intermediate objects, slightly increasing memory consumption
- more concise and elegant, improving readability for anyone who knows streams well
βοΈ Efficiency, complexity and maintainability
In terms of efficiency, Solution 1 is better, as it reduces the number of iterations. However, Solution 2 offers a more elegant approach, ideal if readability is the priority and the data volume is small. In my case I went with the second solution: the list is limited in size, and I wanted to favour cleaner code that's consistent with the project's standards.