Reputation: 1939
I need to transform an arbitrary Collection<T>
into another arbitrary Collection<U>
. For example, I would like to transform an ArrayList<String>
into a HashSet<Integer>
.
I wrote the following code, which gives me a compile-time error on UCollection::new
(Cannot resolve constructor 'UCollection'). I tried replacing it with () -> new UCollection()
, which gives me another compile-time error (Type parameter 'UCollection' cannot be instantiated directly).
import java.util.Collection;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Utils {
public static <T, U, TCollection extends Collection<T>, UCollection extends Collection<U>>
UCollection MappedCollection(TCollection collection, Function<T, U> function) {
return MappedStream(collection.stream(), function).collect(Collectors.toCollection(UCollection::new));
}
public static <T, U> Stream<U> MappedStream(Stream<T> stream, Function<T, U> function) {
return stream.map(function);
}
}
Upvotes: 0
Views: 578
Reputation: 45339
UCollection::new
is invalid because UCollection
is a type variable. You can't construct a type that you don't know in the method.
The easiest fix here is to make your caller supply a UCollection
factory:
public static <T, U, TCollection extends Collection<T>,
UCollection extends Collection<U>>
UCollection MappedCollection(TCollection collection,
Function<T, U> function,
Supplier<UCollection> uCollectionSupplier) {
return MappedStream(collection.stream(), function)
.collect(Collectors.toCollection(uCollectionSupplier));
}
As a side note, I think you have one type variable too many. You could dispense with TCollection
(using C
for UCollection
below)...
public static <T, U, C extends Collection<U>>
C mappedCollection(Collection<T> collection,
Function<T, U> function,
Supplier<C> collectionSupplier) {
return MappedStream(collection.stream(), function)
.collect(Collectors.toCollection(collectionSupplier));
}
Upvotes: 4
Reputation: 109613
I would suggest an output parameter instead of a result, as it allows interface based variables, already providing the correct implementation class. The other answer requires actually passing an ArrayList::new
which is a bit unfortunately, though a result is more functional programming style.
public <P, R> void convert(Collection<P> cp, Collection<R> cr, Function<P, R> mapper) {
cp.stream().map(mapper).forEach(cr::add);
}
List<String> slist = new ArrayList<>();
Collections.addAll(slist, "2", "3", "5", "7", "5", "3");
Set<Integer> iset = new HashSet<>();
convert(slist, iset, Integer::valueOf);
System.out.println(iset);
Stream aggregating and collecting often has such a new collection supplier as parameter as in the other answer. But where the resulting collection is created seems irrelevant. And a result sometimes requires type inference (no interface vars, or needing a cast).
Upvotes: 1
Reputation: 20205
I would suggest a different, in my opinion more readable, solution:
public static <T, U> List<U> convert(Collection<T> collection, Function<T, U> mapper) {
return collection.stream()
.map(mapper)
.collect(Collectors.toList());
}
If you really need to control the exact collection type, then use ernest_k's approach by passing a Supplier<UCollection> supplier
to the method:
public static <T, U, UCollection extends Collection<U>> UCollection convert(
Collection<T> collection,
Function<T, U> mapper,
Supplier<UCollection> collectionSupplier) {
return collection.stream()
.map(mapper)
.collect(Collectors.toCollection(collectionSupplier));
}
Upvotes: 0