I donβt think there is a way around the map. However, you can use groupingBy to hide it, but this is the same code and performance as Pavel Boddington.
List<A> merge(List<A> input) { return input.stream() .collect(groupingBy(a -> a.name))
There is no mutation in the original list, and you can easily change the behavior of merging lists - for example, if you want to get rid of duplicates, just add .distinct() after flatMap(list -> list.numbers.stream()) (remember to add equals to B ) or similarly, you can sort them simply by adding .sorted() (you need to create the B Comparable interface or just use .sorted(Comparator<B>) ).
Here is the complete code with tests and import:
import org.junit.Test; import java.util.List; import static com.shazam.shazamcrest.MatcherAssert.assertThat; import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs; import static java.util.Arrays.asList; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; public class Main { class A { final String name; final List<B> numbers; A(String name, List<B> numbers) { this.name = name; this.numbers = numbers; } } class B { final Integer number; B(Integer number) { this.number = number; } } List<A> merge(List<A> input) { return input.stream() .collect(groupingBy(a -> a.name)) .entrySet() .stream() .map(entry -> new A( entry.getKey(), entry.getValue().stream() .flatMap(list -> list.numbers.stream()) .collect(toList()) )).collect(toList()); } @Test public void test() { List<A> input = asList( new A("abc", asList(new B(1), new B(2))), new A("xyz", asList(new B(3), new B(4))), new A("abc", asList(new B(3), new B(5))) ); List<A> list = merge(input); assertThat(list, sameBeanAs(asList( new A("abc", asList(new B(1), new B(2), new B(3), new B(5))), new A("xyz", asList(new B(3), new B(4))) ))); } }
EDIT:
After your questions in the comments, if you want to add several fields to the groupingBy clause, you will need to create a class that represents the key on the map. If you have fields that you do not want to include in the key, then you must determine how to combine two different values ββ- similar to what you do with numbers. Depending on what the fields are, merging the behavior may be just picking the first value and discarding another (which I did with numbers in the code below).
class A { final String name; final String type; final List<B> numbers; A(String name, String type, List<B> numbers) { this.name = name; this.type = type; this.numbers = numbers; } } class B { final Integer number; B(Integer number) { this.number = number; } } class Group { final String name; final String type; Group(String name, String type) { this.name = name; this.type = type; }