Home > Enterprise >  Filter a List with Java8 only when other List has elements
Filter a List with Java8 only when other List has elements

Time:04-03

I am trying to write code to filter elements from a list if they exist in the another list.

The business logic requires me to return all elements from the first list if the second list is empty.

List<String> list1 = Arrays.asList("a","b","c","d");         
//List<String> list2 = Arrays.asList("a","d");       
List<String> list2 = Collections.emptyList();        

if (!list2.isEmpty()) {
  List<String> finalList = list1.stream()
      .map(x -> x)
      .filter(x -> list2.isEmpty() && list2.contains(x))
      .collect(toList());
  return finalList;
} else {
  return list1;
}

Is there a better way to do it? Preferably, in a single lambda function.

CodePudding user response:

You could try streaming the second list into a set to avoid traversing the entire list each time (O(n^2)) in your filter predicate.

public List<String> filterElementsPresentInBothLists(List<String> list1, List<String> list2) {
        assert list2 != null;
        return list2.stream()
                .collect(Collectors.collectingAndThen(Collectors.toSet(),
                        setView -> setView.isEmpty() ? list1 :
                                list1.stream().filter(setView::contains)
                                        .collect(Collectors.toList())));
    }

CodePudding user response:

Firstly, condition list2.isEmpty() && list2.contains(x) is incorrect, and it'll never get evaluated to true. Because list2 can either be empty or contain a particular object, but not both.

Operation filter() will retain only those elements that match the predicate, hence your stream will always generate an empty list.

Since the possibility of list2 being empty is eliminated by preceding if statement, I assume that the correct filtering condition has to be list2.contains(x).

Is there a better way to do it? Preferably in a single lambda function.

You can achieve that with Java 8 method Collection.removeIf() that expects a Predicate and will remove every element from the source that matches the given predicate.

You don't need initial values in the list1 you can invoke removeIf() directly on it, otherwise you need to make a defensive copy.

For better performance, you need to wrap the second list with a Set, in this case contains() check will be done in a constant time.

Set<String> set2 = new HashSet<>(list2);
list1.removeIf(x -> !set2.contains(x); // element will get removed if not present in the set
return list1;

Or you can generate a new list like that:

Set<String> set2 = new HashSet<>(list2);
return list1.stream()
        .filter(set2::contains)        // element will be preserved if it's present in the set
        .collect(Collectors.toList()); // or `toList()` with Java 16 

CodePudding user response:

I see the mapping also is not required, and the below one satisfies your requirement,

        List<String> list1 = Arrays.asList("a","b","c","d");
        //List<String> list2 = Arrays.asList("a","d");
        List<String> list2 = Collections.emptyList();
        List<String> finalList = list1.stream()
                                    .filter(list2::contains)
                                    .toList(); // Java 16 
        List<String> list = toList.isEmpty() ? list2 : finalList;

        System.out.println(list);
  • Related