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
=======================
Another example
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]]]
And why do you not simply write this ?
How about following?
Produces following :
Car C0
Car C1
Car C2 Engine C2E1 Part DefaultPart Engine C2E2 Part DefaultPart
Have overriden "toString" for Car/Engine/Part classes.
Hope this helps.
you can use
removeIf()
like following:The stream equivalent of
is
The
...
code will however not have access tocar
andengine
.To check for null, you can check in two places:
or