I have a List of custom Foo objects each containing 2 lists of Cat and Dog objects as seen below:
class Foo {
List<Cat> cats;
List<Dog> dogs;
}
class Cat {
int id;
}
class Dog {
String name;
}
I have a method where I pass in a List request and I would like to flatten that to a single Foo object containing each requests dogs and cats flattened together. Is there a clean compact way to do so?
Source List:
List<Cat> catsForOne = new ArrayList<>(); // add a bunch of cats
List<Cat> catsForTwo = new ArrayList<>(); // add a bunch of cats
List<Dog> dogsForTwo = new ArrayList<>(); // add a bunch of dogs
List<Foo> requests = new ArrayList<>();
Foo one = Foo.builder().cats(catsForOne).build();
Foo two = Foo.builder().dogs(dogsForTwo).cats(catsForTwo).build();
requests.add(one);
requests.add(two);
Result should be:
Foo with a List = catsForOne + catsForTwo and a List = dogsForTwo
This is a toy example but you can imagine that foo has about 5-6 collections (ie. Cat, Dog, Pig, Duck etc.) and I would like to have a compact solution with Java 8. The naive one that I can do is to loop over the requests and keep adding all the collections one by one to a final result Foo.
Assume Foo, Cat, Dog etc. are from an external library and thus their source code cannot be altered.
You could just collect them separately and then create a new Foo
object and assign your lists
to it.
List<Dog> collectDogs = foos.stream().flatMap(foo -> foo.getCats().stream())
.collect(Collectors.toList());
List<Cat> collectCats = foos.stream().flatMap(foo -> foo.getDogs().stream())
.collect(Collectors.toList());
and then
Foo result = new Foo();
result.setDogs(collectDogs);
result.setCats(collectCats);
To merge multiple Foo
objects, using Java 8 streams, implement a merge method:
class Foo {
List<Cat> cats;
List<Dog> dogs;
Foo merge(Foo other) {
this.cats.addAll(other.cats);
this.dogs.addAll(other.dogs);
return this;
}
}
You can now easily merge all Foo
objects in a List
into a single Foo
object, using the reduce()
stream method.
List<Foo> requests = ...;
Foo merged = requests.stream().reduce(new Foo(), Foo::merge);
UPDATE
The above code is not safe for parallel use, since the merge()
method updates the current object. For parallel use, the Foo
object should be treated as immutable.
Also, question was enhanced to say that class Foo
is from external library, and cannot be changed, so the merge()
method needs to be separate.
So, do it like this:
class Test {
public static void test() {
List<Foo> requests = ...;
Foo merged = requests.stream().reduce(new Foo(), Test::mergeFoos);
}
public static Foo mergeFoos(Foo a, Foo b) {
Foo merged = new Foo();
merged.cats = mergeLists(a.cats, b.cats);
merged.dogs = mergeLists(a.dogs, b.dogs);
merged.pigs = mergeLists(a.pigs, b.pigs);
merged.ducks = mergeLists(a.ducks, b.ducks);
return merged;
}
private static <E> List<E> mergeLists(List<E> a, List<E> b) {
List<E> merged = new ArrayList<>(a.size() + b.size());
merged.addAll(a);
merged.addAll(b);
return merged;
}
}
If you don't want a Foo
created when the list is empty, you do it like this:
Optional<Foo> merged = requests.stream().reduce(Test::merge);
This will do,
Foo foo = requests.stream().reduce(new Foo(), (f1, f2) -> {
f1.cats.addAll(f2.cats);
f1.dogs.addAll(f2.dogs);
return f1;
});
As with lambda, it is very easy to introduce bugs. Just keep it simple and write a few extra lines.
Add a function to Foo that will allow you to add the lists together, then loop through your Foos adding them to you one true "flattened" Foo.
class Foo
{
...
flatten(Foo a)
{
cats.addAll(a.cats);
dogs.addAll(a.dogs);
}
}
...
Foo result = new Foo();
for(Foo a: fooList)
{
result.flatten(a);
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.