Oleksandr Riznyk
Oleksandr Riznyk

Reputation: 788

Java 8 streams filtering with priority

I have code that looks like:

for (SomeObject object : objects) {
    if (object.getSomething() == Something.SomethingHighPriority) {
        return object;
    }
}

for (SomeObject object : objects) {
    if (object.getSomething() == Something.SomethingLowPriority) {
        return object;
    }
}

It is used for getting the first element in collection by some condition. Also, the priority is important. I need firstly look for the one element and just if it doesn't exist look for the second one.

I want to rewrite it using Stream API but I know that streams are used just once. For now I rewrote it in such way but it seems more ugly than before.

Optional<SomeObject> object = 
    objects.stream()
           .filter(object -> object.getSomething() == Something.SomethingHighPriority)
           .findFirst();

if (object.isPresent()) {
    return object.get();
}

object = objects.stream()
                .filter(object -> object.getSomething() == Something.SomethingLowPriority)
                .findFirst();

// No other use cases
return object.orElse(null);

Is it possible to make it less boilerplate?

Upvotes: 8

Views: 4581

Answers (3)

Bogdan
Bogdan

Reputation: 412

Not sure what SomethingHighPriority means to you but you can try with Comparator. First create two predicates for high and low. Add those to the filter with the or operator beacuse you need both to compare and get the max.

    Predicate<SomeObject> somethingHighPriority= e -> e.getSomething() == Something.SomethingHighPriority;
    Predicate<SomeObject> somethingLowPriority= e -> e.getSomething() == Something.SomethingLowPriority;
    Optional<SomeObject> first = objects.stream()
            .filter(somethingHighPriority.or(somethingLowPriority)) 
            .max(Comparator.comparing(objects::getSomething()));

    if (first.isPresent()) {
        return first.get();
    }
    return null;

The max will handle the priority

Upvotes: 0

fps
fps

Reputation: 34460

I think your code is OK, iterating a collection twice is not a bad approach.

But if you want to do it in only one pass, you could collect to a Map:

Map<Something.Priority, Something> map = objects.stream()
    .filter(o -> o.getSomething() == Something.SomethingHighPriority
              || o.getSomething() == Something.SomethingLowPriority)
    .collect(Collectors.toMap(
                SomeObject::getSomething,
                Function.identity(),
                (oldObject, newObject) -> oldObject));

SomeObject highPriority = map.get(Something.SomethingHighPriority);

return highPriority == null ? map.get(Something.SomethingLowPriority) : highPriority;

Upvotes: 2

Eran
Eran

Reputation: 393866

You can chain the two pipelines:

return objects.stream()
              .filter(object -> object.getSomething() == Something.SomethingHighPriority)
              .findFirst()
              .orElseGet(() -> objects.stream()
                                      .filter(object -> object.getSomething() == Something.SomethingLowPriority)
                                      .findFirst()
                                      .orElse(null));

An alternative would be to sort the Stream by object.getSomething() in descending order and then return the first element (if it has one of the two required values), but that would take O(NlogN) which is less efficient.

Upvotes: 9

Related Questions