简体   繁体   中英

Merging a List of objects containing lists

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM