jlp
jlp

Reputation: 1706

Java Stream filtering on same fields but different criteria

I have a Person class:

@Data
public class Person {
   private Integer id;
   private String status;
}

And I have a List of Person called personList:

[{ 
    "id": null,
    "status": "inactive"
 },
 { 
    "id": 2,
    "status": "inactive"
 },
 { 
    "id": null,
    "status": "active"
 }]

Now I need to find all people whose status is "inactive" regardless if the person has an Id or not. If a person doesn't have an Id, but the status is "active", include that person as well. I am using Java stream to do the filtering:

 List<Person> inactivePersonList = 
     personList.stream()
               .filter(person -> person.getStatus().equals("inactive"))
               .filter(person -> person.getId() == null && person.getStatus().equals("active"))
               .collect(Collectors.toList());

The result after streaming is that nobody got picked up. The first person didn't get picked up because it didn't pass the second filter, the last person didn't get picked up because it didn't pass the first filter.

I think I can fix it by combining the two filters into one using OR, but I am trying to make each filter simple, so before I make the change, I would like to ask if there is a better way to do it. Thanks.

Upvotes: 2

Views: 641

Answers (2)

John Kugelman
John Kugelman

Reputation: 361605

Filters are additive. They're effectively &&-ed together. When you want || a long filter is unavoidable.

List<Person> inactivePersonList = 
    personList.stream()
              .filter(p -> p.getStatus().equals("inactive") ||
                           p.getId() == null && p.getStatus().equals("active"))
              .collect(Collectors.toList());

If you had separate Predicate objects you could .or() them together.

Predicate<Person> inactive = p -> p.getStatus().equals("inactive");
Predicate<Person> activeUnknown = p -> p.getId() == null && p.getStatus().equals("active");

List<Person> inactivePersonList = 
    personList.stream()
              .filter(inactive.or(activeUnknown))
              .collect(Collectors.toList());

Or even:

Predicate<Person> inactive = p -> p.getStatus().equals("inactive");
Predicate<Person> unknown = p -> p.getId() == null;
Predicate<Person> active = p -> p.getStatus().equals("active");

List<Person> inactivePersonList = 
    personList.stream()
              .filter(inactive.or(unknown.and(active)))
              .collect(Collectors.toList());

Upvotes: 6

Maarten Bodewes
Maarten Bodewes

Reputation: 93968

Use one filter, but add a method to the user, e.g. isActive():

@Data
public class Person {
   private Integer id;
   private String status;
   public boolean isActive() {
       return status.equals("active") || id != null;
   }
}

Preferably you should change the way active users are distinguished. If the user is "active" but not active at the same time, then something is going wrong with your class design.

And you might want to use a enum Status { INACTIVE, ACTIVE } instead of the string.

Or use an additional method isUnknown so that your filter is more clear. It doesn't seem to be a good idea to iterate over all persons multiple times.

Upvotes: 0

Related Questions