How do you filter nested loops using java8 streams and filters?
Suppose I have a list of cars (List<Car>
), each car having a list of Engines (List<Engine>
), each engine having a List<Parts>
.
In regular Java this structure can be described as:
for(Car car : cars) {
for (Engine engine : car.getEngines()) {
for (Part part : engine.getParts()) {
// ...
}
}
}
Suppose I initialise the list of cars as:
List<Car> cars = new ArrayList<Car>(Arrays.asList(new Car(), new Car(), new Car()));
cars.get(0).setEngines(null);
cars.get(1).setEngines(new ArrayList<Engine>());
cars.get(2).setEngines(new ArrayList<Engine>() {{
add(new Engine());
add(null);
add(new Engine());
}});
If I want to filter out nulls of List<Engine>
, then I would do
cars.stream().filter(p -> p.getEngines() != null).forEach(System.out::println);
If I want to filter out empty arraylists of List, then I would do
cars.stream().filter(p -> !p.getEngines().isEmpty()).forEach(System.out::println);
But how do I remove the null Engine
in a 3rd car and yet keep two other engines attached to the original list structure? In other words, can we go into the 2nd, 3rd, nth level of hierarchy with Java 8 filters or do filters only work on the top-most layer? I also tried to use .anyMatch()
, without much luck.
just to further clarify, consider the following example: I have 3 cars in my garage. Each car has 3 placeholders for engine. Each engine has 3 placeholders for parts that make up the engine:
Car #1:
Engine#1: part1, part2, part3
Engine#2: null, part2, empty
Engine#3: part1, null, part3
Car #2:
Engine#1: part1, part2, part3
empty: null, null, null
null: null, null, null
Car #3:
Engine#1: null, empty, part3
Engine#2: null, part2, empty
Engine#3: part1, null, part3
Question: how do we use Java 8 .filter, such that when after filtering I get the following:
Car #1:
Engine#1: part1, part2, part3
Engine#2: part2,
Engine#3: part1, part3
Car #2:
Engine#1: part1, part2, part3
Car #1:
Engine#1: part3
Engine#2: part2,
Engine#3: part1,part3
=======================
Guys I hope this example that I just made up is clearer:.. Essentially it is the same as above only it is more verbose and instead of cars we can think of banks to minimize abstraction. For conciseness I make all fields public, I hope you don't mind.
Suppose I am affiliated with 4 banks in my 'bank wallet'
Bank#1:
I physically bank here. I am forced to have 3 accounts, but only are 2 filled with some cash and 3rd is yet to opened (ie null)
Bank #2:
I plan to bank here. Account support structure is created (empty ArrayList), but no accounts are added
Bank #3:
I filled out some marketing form. They have me in their CRM but no accounts will ever be opened
Bank #4:
This bank burned down, there is an artifact placeholder in the wallet, which is null.
The following code describes this:
public class Bank_Wallet {
public static void main(String[] args) {
List<Bank> banks = new ArrayList<Bank>(Arrays.asList(new Bank(), new Bank(), new Bank(), null));
// 1st bank with physical accounts, but one of them is null
banks.get(0).accounts = Arrays.asList(new Account(), null, new Account());
// 2nd bank with empty accounts
banks.get(1).accounts = new ArrayList<Account>();
System.out.println("RAW original");
banks.stream().forEach(System.out::println);
System.out.println("\nFiltered result... ");
banks.stream()// get stream
.filter(p -> p != null) // get rid of null banks
.filter(p -> p.accounts != null) // get rid of null accounts
.filter(p -> !p.accounts.isEmpty()) // get rid of empty accounts
// .filter(p->p.accounts. ?????? ??) ?? how do I remove null account from the remaining bank entry?
.forEach(System.out::println);
}// main
}
The support classes are here:
public class Bank {
public String name;
public static int counter = 0;
public List<Account> accounts;
public Bank() {
this.name = "Bank: #" + Bank.counter++;
}
@Override
public String toString() {
return "Bank [name=" + this.name + ", accounts=" + this.accounts + "]";
}
public class Account {
public String name;
public static int counter;
public Account() {
this.name = "Account: " + Account.counter++;
}
@Override
public String toString() {
return "Account [name=" + this.name + "]";
}
}
when you run this code you will see that after suggested filtering all I am left with is
Bank [name=Bank: #0, accounts=[Account [name=Account: 0], null, Account [name=Account: 1]]]
Question: What other filter do I need do add to the code to get the above result not show null in the accounts and yet retain the overall structure (Bank->Account->etc->etc)
Bank [name=Bank: #0, accounts=[Account [name=Account: 0], Account [name=Account: 1]]]
Streams Filtering & Slicing Basics: Java 8 Streams support declarative filtering out of elements along with the ability to slice-off portions of a list. Streams support four operations to achieve this – filter() , distinct() , limit(n) and skip(n) .
Stream filter() in Java with examples Stream filter(Predicate predicate) returns a stream consisting of the elements of this stream that match the given predicate. This is an intermediate operation.
The filter() function of the Java stream allows you to narrow down the stream's items based on a criterion. If you only want items that are even on your list, you can use the filter method to do this. This method accepts a predicate as an input and returns a list of elements that are the results of that predicate.
Performance. We have seen that using multiple filters can improve the readability of our code.
The stream equivalent of
for(Car car : cars) {
for (Engine engine : car.getEngines()) {
for (Part part : engine.getParts()) {
// ...
}
}
}
is
cars.stream()
.flatMap(car -> car.getEngines().stream())
.flatMap(engine -> engine.getParts().stream())
.forEach(part -> { /* ... */ });
The ...
code will however not have access to car
and engine
.
To check for null, you can check in two places:
cars.stream()
.flatMap(car -> car.getEngines().stream())
.filter(engine -> engine != null)
.flatMap(engine -> engine.getParts().stream())
.forEach(part -> { /* ... */ });
or
cars.stream()
.flatMap(car -> car.getEngines()
.stream()
.filter(engine -> engine != null))
.flatMap(engine -> engine.getParts().stream())
.forEach(part -> { /* ... */ });
And why do you not simply write this ?
cars.stream()
.filter(car -> notEmpty(car.getEngines()))
.filter(car -> car.getEngines().stream().allMatch(engine -> notEmpty(engine.getParts())))
.forEach(System.out::println);
public static <T> boolean notEmpty(Collection<T> collection) {
return collection != null && !collection.isEmpty();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With