Reputation: 64905
I apologize in advance for the terrible title, suggestions for improvement are eagerly accepted.
Let's say I have a method which filters a List<T>
of arbitrary type based on a class
, returning a new List
whose elements are those of the input list that are instances of the given class. Here's a straightforward implementation (yes, you can also do it in a 1-liner with streams):
public static <T> List<T> filterByClass(List<?> list, Class<T> clazz) {
List<T> filtered = new ArrayList<>();
for (Object o : list) {
if (clazz.isInstance(o)) {
filtered.add(clazz.cast(o));
}
}
return filtered;
}
This works great if you pass it a list of a non-generic type like String
as in (prints foo bar
):
List<Object> l = Arrays.<Object>asList(1, "foo ", 2, "bar ");
filterByClass(l, String.class).stream().forEach(System.out::println);
Now I want to pass to filter on a class of a generic type, say Optional<T>
:
List<Object> l = Arrays.<Object>asList(1, Optional.of("foo"), 2, Optional.of("bar"));
filterByClass(l, Optional.class).stream().forEach(System.out::println);
That works fine and prints:
Optional[foo]
Optional[bar]
The problem is there is a raw type hidden there. The return type of the filterByClass
call above is List<Optional>
, not List<Optional<...>>
. Many uses will trigger warnings about raw types, etc.
Now I understand type erasure, and I know that a class
object will never carry generic type information - there is no such Optional<String>.class
or Optional<Integer>.class
- there is only Optional.class
.
However, there is still a better return value than the raw type: I would like instead the fully-generic wildcard version: List<Optional<?>>
. This should be totally type safe, since any Optional
is an Optional<?>
, right?
Assignment doesn't work, since List<Optional>
is not convertible to List<Optional<?>>
List<Optional<?>> r = filterByClass(l, Optional.class);
A cast doesn't work either because the types aren't even castable (in the same way that Foo<T>
can never be directly cast to Foo<U>
no matter the relationship between different types T
and U
).
The only solution seems to be to cast all the way down to a raw List
and then back up to the list with wildcard-paramaterized type-parameter:
List<Optional<?>> r = (List)filterByClass(l, Optional.class);
Now obviously such casts aren't safe in the general case, and this would be totally unsafe if the type parameter in the assignment Optional<?>
didn't match the class
object passed to filterByClass
- although I think they are safe in the specific case that the class
matches the type parameter with unbounded wildcards.
Is there some way to do this without the potentially unsafe casts, either by changing the filterByClass
method or some safe casting of the result?
Another possible answer would be that this is not safe (i.e., the result of filterByClass(..., Optional.class)
cannot safely be converted to List<Optional<?>>
, so the question is ill-formed.
Upvotes: 3
Views: 530
Reputation: 33855
You could change the signature of filterByClass
to:
public static <T> List<T> filterByClass(List<?> list, Class<? extends T> clazz) {...}
Then you can have T
be inferred from the assignee:
List<Object> l = Arrays.<Object>asList(1, Optional.of("foo"), 2, Optional.of("bar"));
List<Optional<?>> result = filterByClass(l, Optional.class);
This works since a raw Optional
is assignable to Optional<?>
(which is safe), so Class<Optional>
satisfies Class<? extends Optional<?>>
, and you can add the Optional
returned by cast
to the list of Optional<?>
.
Upvotes: 4