0

I have a small list which I have to separate into 2 lists based the value of an element. I am looking at 2 ways to do this -

  1. Either iterate the list twice using filters.
List<String> sublist1 = list.stream().filter(condition1).collect(ImmutableList.toImmutableList());
List<String> sublist2 = list.stream().filter(condition2).collect(ImmutableList.toImmutableList());
  1. Use a forEach in streams to assign values
List<String> sublist1 = new LinkedList<>();
List<String> sublist2 = new LinkedList<>();
list.stream().forEach(element -> {
    if(condition1) sublist1.add(element);
    else if (condition2) sublist2.add(element);
})

I wanted to know which way is better and more efficient to implement this?

5
  • Of ofcourse when you iterate just once rather than twice. For a specific case, one can also benchmark how significantly.
    – Naman
    Commented Jan 11, 2021 at 17:57
  • 1
    Looks like you're "partitioning your list", see baeldung.com/java-list-split#use-java8-to-partition-the-list for single-iteration options with Java8.
    – sp00m
    Commented Jan 11, 2021 at 18:03
  • 1
    In addition to iterating through everything twice, the first example presumably involves copying the entire resultant collection at the end. (if you think about how an immutable collector must work, it is likely to mutably accrue elements, before finally creating a new immutable list from the mutable one as the last step)
    – Michael
    Commented Jan 11, 2021 at 18:05
  • By the way, you should almost always prefer ArrayList to LinkedList. You could initialize them both to the size of the original list, which is likely to be larger than you need, but it's better than potentially resizing multiple times to reach the necessary capacity
    – Michael
    Commented Jan 11, 2021 at 18:05
  • list.stream().forEach() in the second case seems to be redundant, can be replaced with list.forEach or an equivalent loop for(E element: list) Commented Jan 12, 2021 at 12:19

1 Answer 1

2

If you have specifically two conditions resulting in two sub-lists and they are alternate that is only one of the two conditions may be true, you could use OR in filter or Predicate.or and then use Collectors.partitioningBy by either condition:

Map<Boolean, List<String>> sublistMap = list
        .stream()
        .filter(condition1.or(condition2))
        .collect(Collectors.partitioningBy(condition1, Collectors.toUnmodifiableList()));

List<String> sublist1 = sublistMap.get(Boolean.TRUE); // condition1
List<String> sublist2 = sublistMap.get(Boolean.FALSE); // condition2

Test

List<String> list = Arrays.asList("a", "b", "cc", "ddd", "aaaa", "vvv", "oo");
Predicate<String> condition1 = (s) -> s.contains("o");
Predicate<String> condition2 = (s) -> s.contains("a");

System.out.println(sublist1);
System.out.println(sublist2);

Output:

[oo]
[a, aaaa]
4
  • My usecase was more of a String compare of value in one of the attribute in the class object. So partitioningBy won't work in my case.
    – Arpit
    Commented Jan 12, 2021 at 9:44
  • Do you mean that both condition1 and condition2 may be true at the same time? Commented Jan 12, 2021 at 9:57
  • While this is a cool trick, I believe it's neither faster nor more readable than any of the approaches from the original question.
    – apangin
    Commented Jan 12, 2021 at 11:03
  • 1
    @apangin, FYI: Java 8: best way to transform a list: map or forEach Commented Jan 12, 2021 at 12:23

Not the answer you're looking for? Browse other questions tagged or ask your own question.