I have two classes Foo and Bar, where Foo has a list of Bars in its attributes. Something like this:
public class Bar {
int id;
...
public boolean isGood() {...}
}
public class Foo {
int id;
List<Bar> bars;
...
public boolean hasBadBar() {
boolean hasOnlyGoodBars = bars.stream().allMatch(Bar::isGood);
return !hasOnlyGoodBars;
}
}
Now imagine I have a list of Foos called foos and want to go through foos and report Foos that have Bars that are bad (bar.isGood() is false). I want to report the pair of (String version of Foo id, String version of Bar id) of such incidents. Any ideas on how to do it in line with stream()?
I can report ids of such Foos (or such Bars) separately but I don't know how to do a pair of them.
CodePudding user response:
This should do the trick:
List<Pair<Foo,Bar>> pairs = foos.stream().
flatMap(
//take all the non-good Bars and create a Pair together with its Foo
foo -> foo.bars.stream().
filter(Predicate.not(Bar::isGood)).
map(bar -> new Pair(foo, bar))
).
collect(Collectors.toList());
Where Pair is some class Pair<A,B>
CodePudding user response:
since you need to return a list of lists you may want to use flatMap to collect everything
List<Pair> fooBarPair = foos.stream().flatMap( foo -> {
List<Bar> badBars = foo.getBadBars();
return badBars.stream().map( bar -> new Pair( foo.toString(), bar.toString() ) );
} ).toList();
public class Test {
@AllArgsConstructor
@ToString(exclude = "isGood")
static class Bar {
int id;
boolean isGood;
public boolean isGood() {return isGood;}
public boolean isBad() {return !isGood;}
}
@AllArgsConstructor
@ToString(exclude = "bars")
@Getter
public class Foo {
int id;
List<Bar> bars;
public boolean hasBadBar() {
return bars.stream().filter(Bar::isBad).toList().size() != bars.size();
}
public List<Bar> getBadBars() {
return bars.stream().filter( Bar::isBad ).collect( Collectors.toList());
}
}
@AllArgsConstructor
@ToString
static class Pair {
String foo;
String bar;
}
@org.junit.jupiter.api.Test
void test() {
List<Foo> foos = new ArrayList<>();
List<Bar> barsFoo0 = new ArrayList<>();
barsFoo0.add( new Bar( 1, true ) );
barsFoo0.add( new Bar( 2, true ) );
barsFoo0.add( new Bar( 3, false ) );
List<Bar> barsFoo1 = new ArrayList<>();
barsFoo1.add( new Bar( 1, false ) );
barsFoo1.add( new Bar( 2, false ) );
barsFoo1.add( new Bar( 3, true ) );
foos.add( new Foo( 0, barsFoo0 ) );
foos.add( new Foo( 1, barsFoo1 ) );
List<Pair> fooBarPair = foos.stream().flatMap( foo -> {
List<Bar> badBars = foo.getBadBars();
return badBars.stream().map( bar -> new Pair( foo.toString(), bar.toString() ) );
} ).toList();
System.out.println(fooBarPair);
}
}
CodePudding user response:
Assuming that 1) you only cared about the first bad bar, and 2) you added an Optional firstBadBar() method to Foo, this code would work to collect the ids into a map:
Map<String,String> result = foos.stream()
.filter( foo -> foo.firstBadBar().isPresent() )
.collect( Collectors.toMap( foo -> String.valueOf( foo.id ), foo -> String.valueOf( foo.firstBadBar().get().id ) ) );
CodePudding user response:
The following should work if no collecting required:
foos.stream()
.filter(f ->f.hasBadBars())
.forEach(f -> {
System.out.printf("Foo %d has bad bars:%n", f.getId());
f.getBars().stream()
.filter(b -> !b.getIsGood())
.forEach(bad ->System.out.printf("\tId of bad Bar: %d%n", bad.getId()));
});
CodePudding user response:
You can create a Map<Foo, List<Bar>> (mapping a Foo to a List<Bar> containing all the bad Bars associated with the Foo).
First, you have a List<Foo> foos.
Then, you can use stream():
Map<Foo, List<Bar>> result = foos.stream()
.filter(x -> x.hasBadBar())
.map(x -> {
List<Bar> badBars = new ArrayList<>(x.bars);
badBars.removeIf(bar -> bar.isGood());
return new Pair<>(x, badBars);
})
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
The filter method is used only to keep the Foo objects that have bad Bars, then the map method is used to create Pair<Foo, List<Bar>> objects for each Foo object, with the original Foo object as the key, and a list of bad Bars as the value. Finally, the collect method is used to collect the elements of the stream into a Map.
Then, you can use the Map to print a Foo and all the corresponding bad Bars
