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 B
s 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); }