I have two classes Foo
and Bar
, where Foo
has a list of Bar
s 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 Foo
s called foos
and want to go through foos
and report Foo
s that have Bar
s 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 id
s 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 Bar
s 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 Bar
s, 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 Bar
s 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 Bar
s