user3122166
user3122166

Reputation: 81

Filter list contains multiple objects java

The code below works fine for me now, but it is not future proof, becuase the numbers of if else statments and instanceof. I would like to extend the Transport list with more objects like bicyles, motors etc.... but every time when I add new object I need to add more if else statements and create more instanceof. Does anyone have a better idea or better solution?

private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
    List<Transport> cars = listOfTransport.stream()
                                          .filter(transport -> transport instanceof Cars)
                                          .collect(Collectors.toList());
    
    List<Transport> airPlanes = listOfTransport.stream()
                                               .filter(transport -> transport instanceof Airplanes)
                                               .collect(Collectors.toList());
   
    if (!cars.isEmpty()){
        return cars.get(refNr);
    } else if (!airPlanes.isEmpty()) {
       return airPlanes.get(refNr);
    } else {
      return null;
    }
}

Upvotes: 0

Views: 712

Answers (4)

MC Emperor
MC Emperor

Reputation: 22977

Well, you could do the following.

First, define your order:

static final List<Class<? extends Transport>> ORDER = List.of(
    Car.class,
    Airplane.class
);

Then, you could write the following method:

private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
    Map<Class<? extends Transport>, Transport> map = listOfTransport.stream()
        .collect(Collectors.groupingBy(Transport::getClass, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(refNr))));

    return ORDER.stream()
        .filter(map::containsKey)
        .map(map::get)
        .findFirst()
        .orElse(null);
}

What this does, is mapping each distinct Class to the refNrth element which is a subtype of the respective class.

Then it walks over ORDER and checks if an element has been found within the original listOfTransport. The key won't exist in the map if listOfTransport does not contain any element of the particular class.


Note that if any element of a particular class exists in the map, the number of elements of that class is assumed to be at least refNr, otherwise an IndexOutOfBoundsException is thrown. With other words, each transport must occur 0 or at least refNr times within the listOfTransport.

Also note that getClass() does not necessarily yield the same result as instanceof. However, I have assumed here that each respective transport does not have further subclasses.

Upvotes: 0

Eritrean
Eritrean

Reputation: 16498

Just as you currently prioritize cars over planes, as your transport types grow you also need some kind of priority on which to return preferentially. You can solve this with an enum. You only need to expand your enum accordingly as soon as you add a new transport type. The enum could look something like:

enum Priority{
    Car(1), 
    Airplane(2);
    
    private int value;
    Priority (int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}

Then you can refactor your method by grouping the elements of your list by their simple class names and adding them to a sorted map using the priority you define in your enum. You can then use the first entry of the map to determine the return value. Example:

private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
    Comparator<String> comp = Comparator.comparingInt(e -> Priority.valueOf(e).getValue());
    
    List<Transport> result = 
            listOfTransport.stream()
                    .collect(Collectors.groupingBy(
                                        e -> e.getClass().getSimpleName(), 
                                        () -> new TreeMap<>(comp), 
                                        Collectors.toList()))
                   .firstEntry().getValue();
     
     return (result != null && 0 <= refNr && refNr < result.size()) ? 
            result.get(refNr) : null;        
}

Upvotes: 1

Gautham M
Gautham M

Reputation: 4935

First group the list elements into a map based on subtype, then create a list of subtypes of transport. Iterate this list and then check if corresponding entry exists in the map:


private static final List<Class> subTypes = List.of(Cars.class, Airplanes.class);

private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
    Map<Class, List<Transport>> map = listOfTransport.stream()
                                                     .collect(Collectors.groupingBy(t -> t.getClass()));
    
    Optional<List<Transport>> op = subTypes.stream()
                                           .filter(map::containsKey)
                                           .findFirst();
    if(op.isPresent()) {
        return op.get().get(refNr); // This could cause IndexOutOfBoundsException
    }else{    
      return null;
    }
}

Upvotes: 0

duffymo
duffymo

Reputation: 308743

Pass in the subtype you want. Maybe this would work:

private static Transport filterObjects(List<Transport> listOfTransport, Class clazz, int refNr) {
    List<Transport> transports = listOfTransport.stream().filter(clazz::isInstance).collect(Collectors.toList());
        
    return !transports.isEmpty() ? transports.get(refNr) : null;
}

Upvotes: 1

Related Questions