Say I want to make a simple method which takes in a collection and cuts the number of its elements:
public static <T extends Collection<?>> T limit(T collection, long limit){
return collection.stream().limit(limit).collect(Collectors.toCollection(???));
}
Is it possible to collect it back to a collection of the generic type?
CodePudding user response:
There's no sane (*) way to create a new collection of the generic type T
inside limit
. Your best bet is to pass a factor for creating that type into the limit
function:
public static <E, T extends Collection<E>> T limit(T collection, long limit, Supplier<T> factory) {
return collection.stream()
.limit(limit)
.collect(Collectors.toCollection(factory));
}
and use it like this:
var limited = limit(List.of(1, 2, 3, 4), 2, ArrayList::new);
(*) You could resort to using reflection, but that would not work with all collection types and thus defeat the purpose of genericity.
CodePudding user response:
Disclaimer: It wouldn't work in any scenario. For instance, it would be no possible to constructusing this code unmodifiable collections like ListN
, SetN
, views of over collections (e.g. sublist()
), synchronized collections, with lists created via Collections.nCopies
, Arrays.asList()
, and with EnumSet
since it has no default constructor.
To provide an empty collection in the Collector of the stream, we can make use of reflection to try to get access to the no-args constructor of the Collection.
public static <T> Collection<T> limit(Collection<T> collection, long limit) {
Class<?> c = collection.getClass();
return collection.stream()
.limit(limit)
.collect(Collectors.toCollection(
() -> (Collection<T>) getCollectionInstance(c)
));
}
public static Collection<?> getCollectionInstance(Class<?> c) {
try {
return (Collection<?>) c.getConstructor().newInstance();
} catch (NoSuchMethodException | InvocationTargetException |
InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
throw new IllegalArgumentException();
}
Usage example:
public static void main(String[] args) {
Deque<Integer> deque = new ArrayDeque<>(List.of(1, 2, 3, 4, 5));
Set<String> set = new HashSet<>(Set.of("A", "B", "C", "D", "E"));
Collection<Integer> dequeCopy = limit(deque, 3);
Collection<String> setCopy = limit(set, 3);
System.out.println(dequeCopy.getClass().getCanonicalName());
System.out.println(dequeCopy);
System.out.println(setCopy.getClass().getCanonicalName());
System.out.println(setCopy);
}
Output:
java.util.ArrayDeque
[1, 2, 3]
java.util.HashSet
[A, B, C]