How to get a separate list of objects with concat in Java 8

I have 2 Java classes.

class A { String name; List<B> numbers; } class B { Integer number; } 

I want to get something different from class A and include list B.

eg. Suppose I have a List with the following objects.

 List<A>{ name = "abc" List<B>{1,2} name= "xyz" List<B>{3,4} name = "abc" List<B>{3,5} } 

The result should be:

 List<A>{ name = "abc" List<B>{1,2,3,5} name="xyz" List<B>{3,4} } 

Any help would be appreciated.

Note. I want to achieve this functionality using Java 8 threads.

thanks

+6
source share
3 answers

You can use the toMap collection:

 Collection<A> result = list.stream() .collect(Collectors.toMap(a -> a.name, a -> a, (a, b) -> {a.numbers.addAll(b.numbers); return a;})) .values(); 

You can copy the result after that into the List (for example, new ArrayList<>(result) ), but since we do not preserve any specific order, having List not very useful. In most scenarios that have Collection , the result is fine.

+3
source

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)) // map created here .entrySet() .stream() .map(entry -> new A( entry.getKey(), entry.getValue().stream() .flatMap(list -> list.numbers.stream()) .collect(toList()) // merging behaviour )).collect(toList()); } 

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; } // this needs to have equals and hashCode methods as we use it as a key in a map } List<A> merge(List<A> input) { return input.stream() .collect(groupingBy(a -> new Group(a.name, a.type))) .entrySet() .stream() .map(entry -> new A( entry.getKey().name, // entry.getKey() is now Group, not String entry.getKey().type, entry.getValue().get(0).numbers // no merging, just take first )).collect(toList()); } 
+2
source

Here is my answer. I added a constructor for A to make it a little easier.

 public class Main { static class A { String name; List<B> numbers; A(String name, List<B> numbers) { this.name = name; this.numbers = new ArrayList<>(numbers); } } static class B { Integer number; } static List<A> merge(List<A> list) { Map<String, List<B>> map = new LinkedHashMap<>(); for (A a : list) map.computeIfAbsent(a.name, k -> new ArrayList<>()).addAll(a.numbers); return map.entrySet() .stream() .map(e -> new A(e.getKey(), e.getValue())) .collect(Collectors.toList()); } } 

There is almost certainly an easy way to replace the first 3 lines of the merge method with what starts with list.stream()... , making the whole method one-line. Although I could not handle it. Perhaps someone else can edit this answer showing how?

+1
source

All Articles