javalistjava-streamjava-11

Filtering from a nested 3-dimensional list of objects using streams in Java


Say that we have a 3-dimensional List of Objects.

class Custom {
  int id;
  List<String> list;
  Custom(int id, List<String> list) { /* Constructor */ }
  // Getters and Setters
}

class CustomContainer {
  int id;
  List<Custom> list;
  CustomContainer(int id, List<Custom> list) { /* Constructor */ }
  // Getters and Setters
}

var l1 = List.of("abc", "def", "acd");
var l2 = List.of("bed", "fed", "aed");
var c1 = new Custom(1, l1);
var c2 = new Custom(2, l2);
var l3 = List.of(c1, c2);
var l4 = List.of(c2);
var cc1 = new CustomContainer(3, l3);
var cc2 = new CustomContainer(4, l4);
var containers = List.of(cc1, cc2);

Say that I want to filter "containers" such that I want to remove all elements from the inner most list that start with "a".

So, the output should be (considering that the toString() prints only the strings in the list):

[[["def"],["bed", "fed"]], [["bed", "fed"]]]

How can I get this using streams in Java?


Solution

  • Since you have to work on the innermost collection, while needing to collect the outermost elements, you could stream the List of CustomContainer and then invoke the methods forEach and removeIf on each element of the list.

    In my original answer, I used the intermediate operation peek. However, this method should mostly be used for debugging purposes.

    This method exists mainly to support debugging

    List<CustomContainer> res = containers.stream()
            .peek(container -> container.getList().forEach(custom -> custom.getList().removeIf(str -> str.startsWith("a"))))
            .collect(Collectors.toList());
    

    An alternative and more viable solution could employ the map method.

    List<CustomContainer> res = containers.stream()
            .map(container -> {
                container.getList().forEach(custom -> custom.getList().removeIf(str -> str.startsWith("a")));
                return container;
            })
            .collect(Collectors.toList());
    

    Side note on List.of()

    I assume that in your snippet you've used List.of() for brevity. However, the method returns an immutable list and any attempt of modification will throw an UnsupportedOperationException. You might want to write your code as follows, relying on the conversion constructor of the ArrayList class:

    var l1 = new ArrayList(List.of("abc", "def", "acd")); //Conversion constructor
    var l2 = new ArrayList(List.of("bed", "fed", "aed")); //Conversion constructor
    var c1 = new Custom(1, l1);
    var c2 = new Custom(2, l2);
    var l3 = new ArrayList(List.of(c1, c2)); //Conversion constructor
    var l4 = new ArrayList(List.of(c2)); //Conversion constructor
    var cc1 = new CustomContainer(3, l3);
    var cc2 = new CustomContainer(4, l4);
    var containers = new ArrayList<>(List.of(cc1, cc2)); //Conversion constructor