Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - Return list of specified generic type

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

like image 750
ms813 Avatar asked Dec 11 '22 19:12

ms813


2 Answers

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.

like image 107
Tunaki Avatar answered Dec 26 '22 16:12

Tunaki


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:

  • Assuming 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)

like image 27
Thomas Avatar answered Dec 26 '22 17:12

Thomas