I have a list of objects that contains various sub-types:
List<Dog> dogList = new ArrayList<>();
dogList.add(new Poodle())
dogList.add(new Dalmation());
I want to search this list and return all instances of a specified class:
public <T extends Dog> List<T> getAll(Class<T> dogType){
List<T> returnObjects = new ArrayList<>();
for(T obj : dogList){
if(dogType.instanceOf(obj){
returnObjects.add(dogType.cast(obj));
}
}
return returnObjects;
}
Usage:
List<Poodle> poodleList = getAll(Poodle.class);
This is my first proper foray into generics, and things feel like they are getting a bit out of control. Is this a decent way of doing things, or am I missing a more simple/obvious solution?
Edit: updated to use instanceOf() and cast() to avoid unchecked cast
Since you mentioned you are using Java 8, you could use the Stream API:
public static <T extends Dog> List<T> getAll(Class<T> dogType) {
return dogList.stream()
.filter(dogType::isInstance)
.map(dogType::cast)
.collect(toList());
}
This method will create a Stream
from the list of dogs, filter them by keeping only those that are an instance of the given class, cast to the given class and finally collect that Stream into a List.
Using isInstance
means that if you call the method with Poodle.class
, then all the dogs that are either a Poodle
or a sub-class of Poodle
will be kept. If you only want to keep dogs that are Poodle
, you can use Class.equals(other)
instead.
Also, note that using the cast
method, there is no unchecked warning (this method already handles it) and it is safe to do it here since we filtered the correct elements before: no ClassCastException
can be thrown.
You'd have to declare a return type for the method and check isAssignableFrom()
on the classes:
public <T extends Dog> List<T> getAll(Class<T> dogType){
List<T> returnObjects = new ArrayList<>();
for(Dog obj : dogList){
if( dogType.isInstance(obj) ){ //as mentioned in the edit as well as the comments
returnObjects.add(dogType.cast(obj));
}
}
return returnObjects;
}
That way calling List<Poodle> poodleList = getAll(Poodle.class);
should return only instances of Poodle
or subclasses while getAll(Dog.class)
should return all dogs.
A few notes:
dogList
only contains dogs, you should use the Dog
type here. Otherwise use the generic type of that array/collection (when in doubt it would be Object
).isAssignableFrom()
checks whether the left most class is the same as the passed one or a super type, i.e. Dog.class.isAssignableFrom( Poodle.class )
will be true if Poodle extends Dog
. If you're only after exact matches use dogType.equals(obj.getClass())
instead.Edit: a short note on dogType.isAssignableFrom(obj.getClass())
: this might result in a NPE if obj
is null so you'd have to handle that. On the other hand, using dogType.isInstance(obj)
is easier to read and doesn't suffer from NPE (since dogType
shouldn't be null). (see @Tunaki's answer as well)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With