I'm trying to filter a List of objects that implements a Interface. And I trying to create a generic method for all the classes.
Something like:
interface SomeInterface {
String getFlag();
}
class SomeObject implements SomeInterface {
public String getFlag() {
return "X";
}
}
List<SomeObject> someObjectList = new ArrayList<>();
// Compilation error here
List<SomeObject> filterList = filterGenericList(someObjectList, "X");
private List<?> filterGenericList(List<? extends SomeInterface> objects, String flag) {
return objects.stream()
.filter(it -> it.getFlag().equals(flag))
.collect(Collectors.toList());
}
How do I run away from the compilation Error?
Incompatible types.
Found: 'java.util.List<capture<?>>',
Required: 'java.util.List<SomeObject>'
CodePudding user response:
When you return List<?>
that means the method returns a list of some unknown type. Java doesn't know that it's the same type of list as the input list. To signal that, create a generic type T
and have both the input and output lists be List<T>
.
private <T extends SomeInterface> List<T> filterGenericList(List<T> objects, String flag) {
return objects.stream()
.filter(it -> it.getFlag().equals(flag))
.collect(Collectors.toList());
}
CodePudding user response:
first of all List<?> doesn't have any type information. You expect a List of SomeObject but your method just returns a List of any type.
To achieve your goal you should define your filter method as
private static <T extends SomeInterface> List<T> filterGenericList(List<T> objects, String flag)
In this way you guarantee that the returned list is of the same type like the input list
CodePudding user response:
Unbounded wildcard <?>
also called the unknown type is a way to tell the compiler that a generic type can't be predicted, and therefore the compiler will disallow to assign a list of unknown type to a list of SomeObject
because it's impossible to ensure that this assignment is safe.
If you have a list of objects that belong to different classes implementing SomeInterface
in order to distinguish between them, you can explicitly pass an instance of target class Class<T>
into a method as an argument.
Seems like you're trying to reinvent the same mechanism by utilizing method getFlag()
. It doesn't look reliable because nothing can stop you from making a typo or accidentally implementing getFlag()
in two different classes in such a way that it'll return the same value.
private <T extends SomeInterface> List<T> filterGenericList(List<? extends SomeInterface> objects,
Class<T> targetClass) {
return objects.stream()
.filter(it -> targetClass.isAssignableFrom(it.getClass()))
.map(targetClass::cast)
.collect(Collectors.toList());
}