Skip to content
Advertisement

Combining / merging object members in a list

I got a list of objects from class A in a list. Some of these objects are equal in id and name but not in list <B> , and list b is ALWAYS different.

I need to merge these so that my list is only made out of object a’s with same name and id exists and all the b from same group are collected I can make use of jdk 8 plus utils so streams are ok to use here.. Although I think reflection here is more usable?

PS: I can not change content of a of b class as they are generated classes and no access / expansion possibility

    @Test
    public void test() {
        List.of(new A(1, "a1", List.of(new B(1, "1b"))),
                new A(1, "a1", List.of(new B(2, "2b"))),
                new A(2, "a2", List.of(new B(3, "3b"))));
//expected
        List.of(new A(1, "a1", List.of(new B(1, "1b"), new B(2, "2b"))),
                new A(2, "a2", List.of(new B(3, "3b"))));


    }


    class A {
        public A(int id, String name, List<B> listB) {
            this.id = id;
            this.name = name;
            this.listB = listB;
        }
        int id;
        String name;
        List<B> listB;
    }

    class B {
        public B(int id, String name) {
            this.id = id;
            this.name = name;
        }
        int id;
        String name;
    }

Advertisement

Answer

You could use

record Key(int id, String name) {};

List<A> result = input.stream().collect(
    Collectors.groupingBy(a -> new Key(a.getId(), a.getName()),
        LinkedHashMap::new,
        Collectors.flatMapping(a -> a.getListB().stream(), Collectors.toList())))
    .entrySet().stream()
    .map(e -> new A(e.getKey().id(), e.getKey().name(), e.getValue()))
    .collect(Collectors.toList());

if(!result.equals(expected)) {
    throw new AssertionError("expected " + expected + " but got " + result);
}

This constructs new lists with new A objects, which is suitable for immutable objects. Your use of List.of(…) suggests a preference towards immutable objects. If you have mutable objects and want to perform the operation in-place, you could do

List<A> result = new ArrayList<>(input); // only needed if input is an immutable list

record Key(int id, String name) {};
HashMap<Key,A> previous = new HashMap<>();

result.removeIf(a -> previous.merge(new Key(a.getId(), a.getName()), a, (old, newA) -> {
        var l = old.getListB();
        if(l.getClass() != ArrayList.class) old.setListB(l = new ArrayList<>(l));
        l.addAll(newA.getListB());
        return old;
    }) != a);

if(!result.equals(expected)) {
    throw new AssertionError("expected " + expected + " but got " + result);
}

This removes the duplicates from the list and adds their Bs to the previously encountered original. It does the minimum of changes required to get the intended list, e.g. if there are no duplicates, it does nothing.

If A objects with the same id always have the same name, in other words, there is no need for a key object checking both, you could simplify this approach to

List<A> result = new ArrayList<>(input); // only needed if input is an immutable list

HashMap<Integer,A> previous = new HashMap<>();

result.removeIf(a -> previous.merge(a.getId(), a, (old, newA) -> {
        var l = old.getListB();
        if(l.getClass() != ArrayList.class) old.setListB(l = new ArrayList<>(l));
        l.addAll(newA.getListB());
        return old;
    }) != a);

if(!result.equals(expected)) {
    throw new AssertionError("expected " + expected + " but got " + result);
}
User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement